"%s (state of %qE: %qs -> %qs, origin: %qE)",
custom_desc.m_buffer,
var,
- m_sm.get_state_name (m_from),
- m_sm.get_state_name (m_to),
+ m_from->get_name (),
+ m_to->get_name (),
origin);
else
result = make_label_text
"%s (state of %qE: %qs -> %qs, NULL origin)",
custom_desc.m_buffer,
var,
- m_sm.get_state_name (m_from),
- m_sm.get_state_name (m_to));
+ m_from->get_name (),
+ m_to->get_name ());
custom_desc.maybe_free ();
return result;
}
(can_colorize,
"state of %qs: %qs -> %qs (origin: %qs)",
sval_desc.m_buffer,
- m_sm.get_state_name (m_from),
- m_sm.get_state_name (m_to),
+ m_from->get_name (),
+ m_to->get_name (),
origin_desc.m_buffer);
}
else
(can_colorize,
"state of %qs: %qs -> %qs (NULL origin)",
sval_desc.m_buffer,
- m_sm.get_state_name (m_from),
- m_sm.get_state_name (m_to));
+ m_from->get_name (),
+ m_to->get_name ());
}
else
{
return make_label_text
(can_colorize,
"global state: %qs -> %qs",
- m_sm.get_state_name (m_from),
- m_sm.get_state_name (m_to));
+ m_from->get_name (),
+ m_to->get_name ());
}
}
= make_label_text (can_colorize,
"%s (%qE is in state %qs)",
ev_desc.m_buffer,
- m_var,m_sm->get_state_name (m_state));
+ m_var, m_state->get_name ());
ev_desc.maybe_free ();
return result;
}
if (m_sm)
return make_label_text (can_colorize,
"here (%qE is in state %qs)",
- m_var,
- m_sm->get_state_name (m_state));
+ m_var, m_state->get_name ());
else
return label_text::borrow ("here");
}
state_machine::state_t to,
tree origin ATTRIBUTE_UNUSED) FINAL OVERRIDE
{
- if (from != 0)
+ if (from != m_sm.get_start_state ())
return;
const svalue *var_new_sval
label_text sval_desc = sval->get_desc ();
log ("considering event %i (%s), with sval: %qs, state: %qs",
idx, event_kind_to_string (base_event->m_kind),
- sval_desc.m_buffer, sm->get_state_name (state));
+ sval_desc.m_buffer, state->get_name ());
}
else
log ("considering event %i (%s), with global state: %qs",
idx, event_kind_to_string (base_event->m_kind),
- sm->get_state_name (state));
+ state->get_name ());
}
else
log ("considering event %i", idx);
sval = state_change->m_origin;
}
log ("event %i: switching state of interest from %qs to %qs",
- idx, sm->get_state_name (state_change->m_to),
- sm->get_state_name (state_change->m_from));
+ idx, state_change->m_to->get_name (),
+ state_change->m_from->get_name ());
state = state_change->m_from;
}
else if (m_verbosity < 4)
logger->log ("%s: state transition of %qE: %s -> %s",
m_sm.get_name (),
var,
- m_sm.get_state_name (from),
- m_sm.get_state_name (to));
+ from->get_name (),
+ to->get_name ());
m_new_smap->set_state (m_new_state->m_region_model, var_new_sval,
to, origin_new_sval, m_eg.get_ext_state ());
}
for (sm_state_map::iterator_t iter = smap->begin ();
iter != smap->end ();
++iter)
- total_sm_state += (*iter).second.m_state;
- total_sm_state += smap->get_global_state ();
+ total_sm_state += (*iter).second.m_state->get_id ();
+ total_sm_state += smap->get_global_state ()->get_id ();
}
if (total_sm_state > 0)
/* sm_state_map's ctor. */
sm_state_map::sm_state_map (const state_machine &sm, int sm_idx)
-: m_sm (sm), m_sm_idx (sm_idx), m_map (), m_global_state (0)
+: m_sm (sm), m_sm_idx (sm_idx), m_map (), m_global_state (sm.get_start_state ())
{
}
{
if (multiline)
pp_string (pp, " ");
- pp_printf (pp, "global: %s", m_sm.get_state_name (m_global_state));
+ pp_string (pp, "global: ");
+ m_global_state->dump_to_pp (pp);
if (multiline)
pp_newline (pp);
first = false;
sval->dump_to_pp (pp, simple);
entry_t e = (*iter).second;
- pp_printf (pp, ": %s", m_sm.get_state_name (e.m_state));
+ pp_string (pp, ": ");
+ e.m_state->dump_to_pp (pp);
if (model)
if (tree rep = model->get_representative_tree (sval))
{
bool
sm_state_map::is_empty_p () const
{
- return m_map.elements () == 0 && m_global_state == 0;
+ return m_map.elements () == 0 && m_global_state == m_sm.get_start_state ();
}
/* Generate a hash value for this sm_state_map. */
inchash::hash hstate;
hstate.add_ptr ((*iter).first);
entry_t e = (*iter).second;
- hstate.add_int (e.m_state);
+ hstate.add_int (e.m_state->get_id ());
hstate.add_ptr (e.m_origin);
result ^= hstate.end ();
}
- result ^= m_global_state;
+ result ^= m_global_state->get_id ();
return result;
}
auto_delete_vec <state_machine> checkers;
checkers.safe_push (sm);
extrinsic_state ext_state (checkers);
+ state_machine::state_t start = sm->get_start_state ();
/* Test setting states on svalue_id instances directly. */
{
+ const state_machine::state test_state_42 ("test state 42", 42);
+ const state_machine::state_t TEST_STATE_42 = &test_state_42;
region_model_manager mgr;
region_model model (&mgr);
const svalue *x_sval = model.get_rvalue (x, NULL);
sm_state_map map (*sm, 0);
ASSERT_TRUE (map.is_empty_p ());
- ASSERT_EQ (map.get_state (x_sval, ext_state), 0);
+ ASSERT_EQ (map.get_state (x_sval, ext_state), start);
- map.impl_set_state (x_sval, 42, z_sval, ext_state);
- ASSERT_EQ (map.get_state (x_sval, ext_state), 42);
+ map.impl_set_state (x_sval, TEST_STATE_42, z_sval, ext_state);
+ ASSERT_EQ (map.get_state (x_sval, ext_state), TEST_STATE_42);
ASSERT_EQ (map.get_origin (x_sval, ext_state), z_sval);
- ASSERT_EQ (map.get_state (y_sval, ext_state), 0);
+ ASSERT_EQ (map.get_state (y_sval, ext_state), start);
ASSERT_FALSE (map.is_empty_p ());
map.impl_set_state (y_sval, 0, z_sval, ext_state);
- ASSERT_EQ (map.get_state (y_sval, ext_state), 0);
+ ASSERT_EQ (map.get_state (y_sval, ext_state), start);
map.impl_set_state (x_sval, 0, z_sval, ext_state);
- ASSERT_EQ (map.get_state (x_sval, ext_state), 0);
+ ASSERT_EQ (map.get_state (x_sval, ext_state), start);
ASSERT_TRUE (map.is_empty_p ());
}
+ const state_machine::state test_state_5 ("test state 5", 5);
+ const state_machine::state_t TEST_STATE_5 = &test_state_5;
+
/* Test setting states via equivalence classes. */
{
region_model_manager mgr;
sm_state_map map (*sm, 0);
ASSERT_TRUE (map.is_empty_p ());
- ASSERT_EQ (map.get_state (x_sval, ext_state), 0);
- ASSERT_EQ (map.get_state (y_sval, ext_state), 0);
+ ASSERT_EQ (map.get_state (x_sval, ext_state), start);
+ ASSERT_EQ (map.get_state (y_sval, ext_state), start);
model.add_constraint (x, EQ_EXPR, y, NULL);
/* Setting x to a state should also update y, as they
are in the same equivalence class. */
- map.set_state (&model, x_sval, 5, z_sval, ext_state);
- ASSERT_EQ (map.get_state (x_sval, ext_state), 5);
- ASSERT_EQ (map.get_state (y_sval, ext_state), 5);
+ map.set_state (&model, x_sval, TEST_STATE_5, z_sval, ext_state);
+ ASSERT_EQ (map.get_state (x_sval, ext_state), TEST_STATE_5);
+ ASSERT_EQ (map.get_state (y_sval, ext_state), TEST_STATE_5);
ASSERT_EQ (map.get_origin (x_sval, ext_state), z_sval);
ASSERT_EQ (map.get_origin (y_sval, ext_state), z_sval);
}
ASSERT_EQ (map0.hash (), map1.hash ());
ASSERT_EQ (map0, map1);
- map1.impl_set_state (y_sval, 5, z_sval, ext_state);
+ map1.impl_set_state (y_sval, TEST_STATE_5, z_sval, ext_state);
ASSERT_NE (map0.hash (), map1.hash ());
ASSERT_NE (map0, map1);
/* Make the same change to map2. */
- map2.impl_set_state (y_sval, 5, z_sval, ext_state);
+ map2.impl_set_state (y_sval, TEST_STATE_5, z_sval, ext_state);
ASSERT_EQ (map1.hash (), map2.hash ());
ASSERT_EQ (map1, map2);
}
/* Equality and hashing shouldn't depend on ordering. */
{
+ const state_machine::state test_state_2 ("test state 2", 2);
+ const state_machine::state_t TEST_STATE_2 = &test_state_2;
+ const state_machine::state test_state_3 ("test state 3", 3);
+ const state_machine::state_t TEST_STATE_3 = &test_state_3;
sm_state_map map0 (*sm, 0);
sm_state_map map1 (*sm, 0);
sm_state_map map2 (*sm, 0);
const svalue *y_sval = model.get_rvalue (y, NULL);
const svalue *z_sval = model.get_rvalue (z, NULL);
- map1.impl_set_state (x_sval, 2, NULL, ext_state);
- map1.impl_set_state (y_sval, 3, NULL, ext_state);
- map1.impl_set_state (z_sval, 2, NULL, ext_state);
+ map1.impl_set_state (x_sval, TEST_STATE_2, NULL, ext_state);
+ map1.impl_set_state (y_sval, TEST_STATE_3, NULL, ext_state);
+ map1.impl_set_state (z_sval, TEST_STATE_2, NULL, ext_state);
- map2.impl_set_state (z_sval, 2, NULL, ext_state);
- map2.impl_set_state (y_sval, 3, NULL, ext_state);
- map2.impl_set_state (x_sval, 2, NULL, ext_state);
+ map2.impl_set_state (z_sval, TEST_STATE_2, NULL, ext_state);
+ map2.impl_set_state (y_sval, TEST_STATE_3, NULL, ext_state);
+ map2.impl_set_state (x_sval, TEST_STATE_2, NULL, ext_state);
ASSERT_EQ (map1.hash (), map2.hash ());
ASSERT_EQ (map1, map2);
model0->set_value (model0->get_lvalue (p, &ctxt),
ptr_sval, &ctxt);
sm_state_map *smap = s0.m_checker_states[0];
- const state_machine::state_t TEST_STATE = 3;
+ const state_machine::state test_state ("test state", 0);
+ const state_machine::state_t TEST_STATE = &test_state;
smap->impl_set_state (ptr_sval, TEST_STATE, NULL, ext_state);
ASSERT_EQ (smap->get_state (ptr_sval, ext_state), TEST_STATE);
checkers.safe_push (make_signal_state_machine (NULL));
extrinsic_state ext_state (checkers);
+ const state_machine::state test_state_0 ("test state 0", 0);
+ const state_machine::state test_state_1 ("test state 1", 1);
+ const state_machine::state_t TEST_STATE_0 = &test_state_0;
+ const state_machine::state_t TEST_STATE_1 = &test_state_1;
+
program_state s0 (ext_state);
{
sm_state_map *smap0 = s0.m_checker_states[0];
- const state_machine::state_t TEST_STATE_0 = 0;
smap0->set_global_state (TEST_STATE_0);
ASSERT_EQ (smap0->get_global_state (), TEST_STATE_0);
}
program_state s1 (ext_state);
{
sm_state_map *smap1 = s1.m_checker_states[0];
- const state_machine::state_t TEST_STATE_1 = 1;
smap1->set_global_state (TEST_STATE_1);
ASSERT_EQ (smap1->get_global_state (), TEST_STATE_1);
}
bool can_purge_p (state_t s) const FINAL OVERRIDE;
pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
- /* Start state. */
- state_t m_start;
-
/* State for a FILE * returned from fopen that hasn't been checked for
NULL.
It could be an open stream, or could be NULL. */
label_text describe_state_change (const evdesc::state_change &change)
OVERRIDE
{
- if (change.m_old_state == m_sm.m_start
+ if (change.m_old_state == m_sm.get_start_state ()
&& change.m_new_state == m_sm.m_unchecked)
// TODO: verify that it's the fopen stmt, not a copy
return label_text::borrow ("opened here");
fileptr_state_machine::fileptr_state_machine (logger *logger)
: state_machine ("file", logger)
{
- m_start = add_state ("start");
m_unchecked = add_state ("unchecked");
m_null = add_state ("null");
m_nonnull = add_state ("nonnull");
bool reset_when_passed_to_unknown_fn_p (state_t s,
bool is_mutable) const FINAL OVERRIDE;
- /* Start state. */
- state_t m_start;
-
/* State for a pointer returned from malloc that hasn't been checked for
NULL.
It could be a pointer to heap-allocated memory, or could be NULL. */
label_text describe_state_change (const evdesc::state_change &change)
OVERRIDE
{
- if (change.m_old_state == m_sm.m_start
+ if (change.m_old_state == m_sm.get_start_state ()
&& change.m_new_state == m_sm.m_unchecked)
// TODO: verify that it's the allocation stmt, not a copy
return label_text::borrow ("allocated here");
label_text describe_state_change (const evdesc::state_change &change)
FINAL OVERRIDE
{
- if (change.m_old_state == m_sm.m_start
+ if (change.m_old_state == m_sm.get_start_state ()
&& change.m_new_state == m_sm.m_unchecked)
{
m_origin_of_unchecked_event = change.m_event_id;
malloc_state_machine::malloc_state_machine (logger *logger)
: state_machine ("malloc", logger)
{
- m_start = add_state ("start");
m_unchecked = add_state ("unchecked");
m_null = add_state ("null");
m_nonnull = add_state ("nonnull");
tree rhs) const FINAL OVERRIDE;
bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-private:
- state_t m_start;
};
class pattern_match : public pending_diagnostic_subclass<pattern_match>
pattern_test_state_machine::pattern_test_state_machine (logger *logger)
: state_machine ("pattern-test", logger)
{
- m_start = add_state ("start");
}
bool
bool can_purge_p (state_t s) const FINAL OVERRIDE;
- /* Start state. */
- state_t m_start;
-
/* State for "sensitive" data, such as a password. */
state_t m_sensitive;
sensitive_state_machine::sensitive_state_machine (logger *logger)
: state_machine ("sensitive", logger)
{
- m_start = add_state ("start");
m_sensitive = add_state ("sensitive");
m_stop = add_state ("stop");
}
/* These states are "global", rather than per-expression. */
- /* Start state. */
- state_t m_start;
-
/* State for when we're in a signal handler. */
state_t m_in_signal_handler;
signal_state_machine::signal_state_machine (logger *logger)
: state_machine ("signal", logger)
{
- m_start = add_state ("start");
m_in_signal_handler = add_state ("in_signal_handler");
m_stop = add_state ("stop");
}
bool can_purge_p (state_t s) const FINAL OVERRIDE;
- /* Start state. */
- state_t m_start;
-
/* State for a "tainted" value: unsanitized data potentially under an
attacker's control. */
state_t m_tainted;
taint_state_machine::taint_state_machine (logger *logger)
: state_machine ("taint", logger)
{
- m_start = add_state ("start");
m_tainted = add_state ("tainted");
m_has_lb = add_state ("has_lb");
m_has_ub = add_state ("has_ub");
return POINTER_TYPE_P (TREE_TYPE (var));
}
+
+/* class state_machine::state. */
+
+/* Base implementation of dump_to_pp vfunc. */
+
+void
+state_machine::state::dump_to_pp (pretty_printer *pp) const
+{
+ pp_string (pp, m_name);
+}
+
+/* class state_machine. */
+
+/* state_machine's ctor. */
+
+state_machine::state_machine (const char *name, logger *logger)
+: log_user (logger), m_name (name), m_next_state_id (0),
+ m_start (add_state ("start"))
+{
+}
+
/* Add a state with name NAME to this state_machine.
The string is required to outlive the state_machine.
state_machine::state_t
state_machine::add_state (const char *name)
{
- m_state_names.safe_push (name);
- return m_state_names.length () - 1;
-}
-
-/* Get the name of state S within this state_machine. */
-
-const char *
-state_machine::get_state_name (state_t s) const
-{
- return m_state_names[s];
+ state *s = new state (name, alloc_state_id ());
+ m_states.safe_push (s);
+ return s;
}
/* Get the state with name NAME, which must exist.
This is purely intended for use in selftests. */
state_machine::state_t
-state_machine::get_state_by_name (const char *name)
+state_machine::get_state_by_name (const char *name) const
{
unsigned i;
- const char *iter_name;
- FOR_EACH_VEC_ELT (m_state_names, i, iter_name)
- if (!strcmp (name, iter_name))
- return i;
+ state *s;
+ FOR_EACH_VEC_ELT (m_states, i, s)
+ if (!strcmp (name, s->get_name ()))
+ return s;
/* Name not found. */
gcc_unreachable ();
}
-/* Assert that S is a valid state for this state_machine. */
-
-void
-state_machine::validate (state_t s) const
-{
- gcc_assert (s < m_state_names.length ());
-}
-
/* Dump a multiline representation of this state machine to PP. */
void
state_machine::dump_to_pp (pretty_printer *pp) const
{
unsigned i;
- const char *name;
- FOR_EACH_VEC_ELT (m_state_names, i, name)
- pp_printf (pp, " state %i: %qs\n", i, name);
+ state *s;
+ FOR_EACH_VEC_ELT (m_states, i, s)
+ {
+ pp_printf (pp, " state %i: ", i);
+ s->dump_to_pp (pp);
+ pp_newline (pp);
+ }
}
/* Create instances of the various state machines, each using LOGGER,
extern bool any_pointer_p (tree var);
/* An abstract base class for a state machine describing an API.
- A mapping from state IDs to names, and various virtual functions
+ Manages a set of state objects, and has various virtual functions
for pattern-matching on statements. */
class state_machine : public log_user
{
public:
- typedef unsigned state_t;
+ /* States are represented by immutable objects, owned by the state
+ machine. */
+ class state
+ {
+ public:
+ state (const char *name, unsigned id) : m_name (name), m_id (id) {}
+ virtual ~state () {}
+
+ const char *get_name () const { return m_name; }
+ virtual void dump_to_pp (pretty_printer *pp) const;
+
+ unsigned get_id () const { return m_id; }
- state_machine (const char *name, logger *logger)
- : log_user (logger), m_name (name) {}
+ private:
+ const char *m_name;
+ unsigned m_id;
+ };
+ typedef const state_machine::state *state_t;
+ state_machine (const char *name, logger *logger);
virtual ~state_machine () {}
/* Should states be inherited from a parent region to a child region,
virtual state_machine::state_t get_default_state (const svalue *) const
{
- return 0;
+ return m_start;
}
const char *get_name () const { return m_name; }
- const char *get_state_name (state_t s) const;
-
- state_t get_state_by_name (const char *name);
+ state_t get_state_by_name (const char *name) const;
/* Return true if STMT is a function call recognized by this sm. */
virtual bool on_stmt (sm_context *sm_ctxt,
void dump_to_pp (pretty_printer *pp) const;
+ state_t get_start_state () const { return m_start; }
+
protected:
state_t add_state (const char *name);
+ unsigned alloc_state_id () { return m_next_state_id++; }
private:
DISABLE_COPY_AND_ASSIGN (state_machine);
const char *m_name;
- auto_vec<const char *> m_state_names;
-};
-/* Is STATE the start state? (zero is hardcoded as the start state). */
+ /* States are owned by the state_machine. */
+ auto_delete_vec<state> m_states;
-static inline bool
-start_start_p (state_machine::state_t state)
-{
- return state == 0;
-}
+ unsigned m_next_state_id;
+
+protected:
+ /* Must be inited after m_next_state_id. */
+ state_t m_start;
+};
/* Abstract base class for state machines to pass to
sm_context::on_custom_transition for handling non-standard transitions