145
|
1 /* Classes for representing the state of interest at a given path of analysis.
|
|
2 Copyright (C) 2019-2020 Free Software Foundation, Inc.
|
|
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
4
|
|
5 This file is part of GCC.
|
|
6
|
|
7 GCC is free software; you can redistribute it and/or modify it
|
|
8 under the terms of the GNU General Public License as published by
|
|
9 the Free Software Foundation; either version 3, or (at your option)
|
|
10 any later version.
|
|
11
|
|
12 GCC is distributed in the hope that it will be useful, but
|
|
13 WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
15 General Public License for more details.
|
|
16
|
|
17 You should have received a copy of the GNU General Public License
|
|
18 along with GCC; see the file COPYING3. If not see
|
|
19 <http://www.gnu.org/licenses/>. */
|
|
20
|
|
21 #include "config.h"
|
|
22 #include "system.h"
|
|
23 #include "coretypes.h"
|
|
24 #include "tree.h"
|
|
25 #include "diagnostic.h"
|
|
26 #include "function.h"
|
|
27 #include "analyzer/analyzer.h"
|
|
28 #include "analyzer/analyzer-logging.h"
|
|
29 #include "analyzer/sm.h"
|
|
30 #include "sbitmap.h"
|
|
31 #include "tristate.h"
|
|
32 #include "ordered-hash-map.h"
|
|
33 #include "selftest.h"
|
|
34 #include "analyzer/region-model.h"
|
|
35 #include "analyzer/program-state.h"
|
|
36 #include "analyzer/constraint-manager.h"
|
|
37 #include "alloc-pool.h"
|
|
38 #include "fibonacci_heap.h"
|
|
39 #include "shortest-paths.h"
|
|
40 #include "analyzer/constraint-manager.h"
|
|
41 #include "diagnostic-event-id.h"
|
|
42 #include "analyzer/pending-diagnostic.h"
|
|
43 #include "analyzer/diagnostic-manager.h"
|
|
44 #include "cfg.h"
|
|
45 #include "basic-block.h"
|
|
46 #include "gimple.h"
|
|
47 #include "gimple-iterator.h"
|
|
48 #include "cgraph.h"
|
|
49 #include "digraph.h"
|
|
50 #include "analyzer/supergraph.h"
|
|
51 #include "analyzer/call-string.h"
|
|
52 #include "analyzer/program-point.h"
|
|
53 #include "analyzer/program-state.h"
|
|
54 #include "analyzer/exploded-graph.h"
|
|
55 #include "analyzer/state-purge.h"
|
|
56 #include "analyzer/analyzer-selftests.h"
|
|
57
|
|
58 #if ENABLE_ANALYZER
|
|
59
|
|
60 namespace ana {
|
|
61
|
|
62 /* class extrinsic_state. */
|
|
63
|
|
64 /* Dump a multiline representation of this state to PP. */
|
|
65
|
|
66 void
|
|
67 extrinsic_state::dump_to_pp (pretty_printer *pp) const
|
|
68 {
|
|
69 pp_printf (pp, "extrinsic_state: %i checker(s)\n", get_num_checkers ());
|
|
70 unsigned i;
|
|
71 state_machine *checker;
|
|
72 FOR_EACH_VEC_ELT (m_checkers, i, checker)
|
|
73 {
|
|
74 pp_printf (pp, "m_checkers[%i]: %qs\n", i, checker->get_name ());
|
|
75 checker->dump_to_pp (pp);
|
|
76 }
|
|
77 }
|
|
78
|
|
79 /* Dump a multiline representation of this state to OUTF. */
|
|
80
|
|
81 void
|
|
82 extrinsic_state::dump_to_file (FILE *outf) const
|
|
83 {
|
|
84 pretty_printer pp;
|
|
85 if (outf == stderr)
|
|
86 pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
87 pp.buffer->stream = outf;
|
|
88 dump_to_pp (&pp);
|
|
89 pp_flush (&pp);
|
|
90 }
|
|
91
|
|
92 /* Dump a multiline representation of this state to stderr. */
|
|
93
|
|
94 DEBUG_FUNCTION void
|
|
95 extrinsic_state::dump () const
|
|
96 {
|
|
97 dump_to_file (stderr);
|
|
98 }
|
|
99
|
|
100 /* class sm_state_map. */
|
|
101
|
|
102 /* sm_state_map's ctor. */
|
|
103
|
|
104 sm_state_map::sm_state_map ()
|
|
105 : m_map (), m_global_state (0)
|
|
106 {
|
|
107 }
|
|
108
|
|
109 /* Clone the sm_state_map. */
|
|
110
|
|
111 sm_state_map *
|
|
112 sm_state_map::clone () const
|
|
113 {
|
|
114 return new sm_state_map (*this);
|
|
115 }
|
|
116
|
|
117 /* Clone this sm_state_map, remapping all svalue_ids within it with ID_MAP.
|
|
118
|
|
119 Return NULL if there are any svalue_ids that have sm-state for which
|
|
120 ID_MAP maps them to svalue_id::null (and thus the clone would have lost
|
|
121 the sm-state information). */
|
|
122
|
|
123 sm_state_map *
|
|
124 sm_state_map::clone_with_remapping (const one_way_svalue_id_map &id_map) const
|
|
125 {
|
|
126 sm_state_map *result = new sm_state_map ();
|
|
127 result->m_global_state = m_global_state;
|
|
128 for (map_t::iterator iter = m_map.begin ();
|
|
129 iter != m_map.end ();
|
|
130 ++iter)
|
|
131 {
|
|
132 svalue_id sid = (*iter).first;
|
|
133 gcc_assert (!sid.null_p ());
|
|
134 entry_t e = (*iter).second;
|
|
135 /* TODO: what should we do if the origin maps from non-null to null?
|
|
136 Is that loss of information acceptable? */
|
|
137 id_map.update (&e.m_origin);
|
|
138
|
|
139 svalue_id new_sid = id_map.get_dst_for_src (sid);
|
|
140 if (new_sid.null_p ())
|
|
141 {
|
|
142 delete result;
|
|
143 return NULL;
|
|
144 }
|
|
145 result->m_map.put (new_sid, e);
|
|
146 }
|
|
147 return result;
|
|
148 }
|
|
149
|
|
150 /* Print this sm_state_map (for SM) to PP. */
|
|
151
|
|
152 void
|
|
153 sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
|
|
154 {
|
|
155 bool first = true;
|
|
156 pp_string (pp, "{");
|
|
157 if (m_global_state != 0)
|
|
158 {
|
|
159 pp_printf (pp, "global: %s", sm.get_state_name (m_global_state));
|
|
160 first = false;
|
|
161 }
|
|
162 for (map_t::iterator iter = m_map.begin ();
|
|
163 iter != m_map.end ();
|
|
164 ++iter)
|
|
165 {
|
|
166 if (!first)
|
|
167 pp_string (pp, ", ");
|
|
168 first = false;
|
|
169 svalue_id sid = (*iter).first;
|
|
170 sid.print (pp);
|
|
171
|
|
172 entry_t e = (*iter).second;
|
|
173 pp_printf (pp, ": %s (origin: ",
|
|
174 sm.get_state_name (e.m_state));
|
|
175 e.m_origin.print (pp);
|
|
176 pp_string (pp, ")");
|
|
177 }
|
|
178 pp_string (pp, "}");
|
|
179 }
|
|
180
|
|
181 /* Dump this object (for SM) to stderr. */
|
|
182
|
|
183 DEBUG_FUNCTION void
|
|
184 sm_state_map::dump (const state_machine &sm) const
|
|
185 {
|
|
186 pretty_printer pp;
|
|
187 pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
188 pp.buffer->stream = stderr;
|
|
189 print (sm, &pp);
|
|
190 pp_newline (&pp);
|
|
191 pp_flush (&pp);
|
|
192 }
|
|
193
|
|
194 /* Return true if no states have been set within this map
|
|
195 (all expressions are for the start state). */
|
|
196
|
|
197 bool
|
|
198 sm_state_map::is_empty_p () const
|
|
199 {
|
|
200 return m_map.elements () == 0 && m_global_state == 0;
|
|
201 }
|
|
202
|
|
203 /* Generate a hash value for this sm_state_map. */
|
|
204
|
|
205 hashval_t
|
|
206 sm_state_map::hash () const
|
|
207 {
|
|
208 hashval_t result = 0;
|
|
209
|
|
210 /* Accumulate the result by xoring a hash for each slot, so that the
|
|
211 result doesn't depend on the ordering of the slots in the map. */
|
|
212
|
|
213 for (map_t::iterator iter = m_map.begin ();
|
|
214 iter != m_map.end ();
|
|
215 ++iter)
|
|
216 {
|
|
217 inchash::hash hstate;
|
|
218 inchash::add ((*iter).first, hstate);
|
|
219 entry_t e = (*iter).second;
|
|
220 hstate.add_int (e.m_state);
|
|
221 inchash::add (e.m_origin, hstate);
|
|
222 result ^= hstate.end ();
|
|
223 }
|
|
224 result ^= m_global_state;
|
|
225
|
|
226 return result;
|
|
227 }
|
|
228
|
|
229 /* Equality operator for sm_state_map. */
|
|
230
|
|
231 bool
|
|
232 sm_state_map::operator== (const sm_state_map &other) const
|
|
233 {
|
|
234 if (m_global_state != other.m_global_state)
|
|
235 return false;
|
|
236
|
|
237 if (m_map.elements () != other.m_map.elements ())
|
|
238 return false;
|
|
239
|
|
240 for (map_t::iterator iter = m_map.begin ();
|
|
241 iter != m_map.end ();
|
|
242 ++iter)
|
|
243 {
|
|
244 svalue_id sid = (*iter).first;
|
|
245 entry_t e = (*iter).second;
|
|
246 entry_t *other_slot = const_cast <map_t &> (other.m_map).get (sid);
|
|
247 if (other_slot == NULL)
|
|
248 return false;
|
|
249 if (e != *other_slot)
|
|
250 return false;
|
|
251 }
|
|
252
|
|
253 gcc_checking_assert (hash () == other.hash ());
|
|
254
|
|
255 return true;
|
|
256 }
|
|
257
|
|
258 /* Get the state of SID within this object.
|
|
259 States default to the start state. */
|
|
260
|
|
261 state_machine::state_t
|
|
262 sm_state_map::get_state (svalue_id sid) const
|
|
263 {
|
|
264 gcc_assert (!sid.null_p ());
|
|
265
|
|
266 if (entry_t *slot
|
|
267 = const_cast <map_t &> (m_map).get (sid))
|
|
268 return slot->m_state;
|
|
269 else
|
|
270 return 0;
|
|
271 }
|
|
272
|
|
273 /* Get the "origin" svalue_id for any state of SID. */
|
|
274
|
|
275 svalue_id
|
|
276 sm_state_map::get_origin (svalue_id sid) const
|
|
277 {
|
|
278 gcc_assert (!sid.null_p ());
|
|
279
|
|
280 entry_t *slot
|
|
281 = const_cast <map_t &> (m_map).get (sid);
|
|
282 if (slot)
|
|
283 return slot->m_origin;
|
|
284 else
|
|
285 return svalue_id::null ();
|
|
286 }
|
|
287
|
|
288 /* Set the state of SID within MODEL to STATE, recording that
|
|
289 the state came from ORIGIN. */
|
|
290
|
|
291 void
|
|
292 sm_state_map::set_state (region_model *model,
|
|
293 svalue_id sid,
|
|
294 state_machine::state_t state,
|
|
295 svalue_id origin)
|
|
296 {
|
|
297 if (model == NULL)
|
|
298 return;
|
|
299 equiv_class &ec = model->get_constraints ()->get_equiv_class (sid);
|
|
300 if (!set_state (ec, state, origin))
|
|
301 return;
|
|
302
|
|
303 /* Also do it for all svalues that are equal via non-cm, so that
|
|
304 e.g. (void *)&r and (foo *)&r transition together. */
|
|
305 for (unsigned i = 0; i < model->get_num_svalues (); i++)
|
|
306 {
|
|
307 svalue_id other_sid = svalue_id::from_int (i);
|
|
308 if (other_sid == sid)
|
|
309 continue;
|
|
310
|
|
311 tristate eq = model->eval_condition_without_cm (sid, EQ_EXPR, other_sid);
|
|
312 if (eq.is_true ())
|
|
313 impl_set_state (other_sid, state, origin);
|
|
314 }
|
|
315 }
|
|
316
|
|
317 /* Set the state of EC to STATE, recording that the state came from
|
|
318 ORIGIN.
|
|
319 Return true if any states of svalue_ids within EC changed. */
|
|
320
|
|
321 bool
|
|
322 sm_state_map::set_state (const equiv_class &ec,
|
|
323 state_machine::state_t state,
|
|
324 svalue_id origin)
|
|
325 {
|
|
326 int i;
|
|
327 svalue_id *sid;
|
|
328 bool any_changed = false;
|
|
329 FOR_EACH_VEC_ELT (ec.m_vars, i, sid)
|
|
330 any_changed |= impl_set_state (*sid, state, origin);
|
|
331 return any_changed;
|
|
332 }
|
|
333
|
|
334 /* Set state of SID to STATE, bypassing equivalence classes.
|
|
335 Return true if the state changed. */
|
|
336
|
|
337 bool
|
|
338 sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
|
|
339 svalue_id origin)
|
|
340 {
|
|
341 if (get_state (sid) == state)
|
|
342 return false;
|
|
343
|
|
344 /* Special-case state 0 as the default value. */
|
|
345 if (state == 0)
|
|
346 {
|
|
347 if (m_map.get (sid))
|
|
348 m_map.remove (sid);
|
|
349 return true;
|
|
350 }
|
|
351 gcc_assert (!sid.null_p ());
|
|
352 m_map.put (sid, entry_t (state, origin));
|
|
353 return true;
|
|
354 }
|
|
355
|
|
356 /* Set the "global" state within this state map to STATE. */
|
|
357
|
|
358 void
|
|
359 sm_state_map::set_global_state (state_machine::state_t state)
|
|
360 {
|
|
361 m_global_state = state;
|
|
362 }
|
|
363
|
|
364 /* Get the "global" state within this state map. */
|
|
365
|
|
366 state_machine::state_t
|
|
367 sm_state_map::get_global_state () const
|
|
368 {
|
|
369 return m_global_state;
|
|
370 }
|
|
371
|
|
372 /* Handle CALL to unknown FNDECL with an unknown function body, which
|
|
373 could do anything to the states passed to it.
|
|
374 Clear any state for SM for the params and any LHS.
|
|
375 Note that the function might be known to other state machines, but
|
|
376 not to this one. */
|
|
377
|
|
378 void
|
|
379 sm_state_map::purge_for_unknown_fncall (const exploded_graph &eg,
|
|
380 const state_machine &sm,
|
|
381 const gcall *call,
|
|
382 tree fndecl,
|
|
383 region_model *new_model)
|
|
384 {
|
|
385 logger * const logger = eg.get_logger ();
|
|
386 if (logger)
|
|
387 {
|
|
388 if (fndecl)
|
|
389 logger->log ("function %qE is unknown to checker %qs",
|
|
390 fndecl, sm.get_name ());
|
|
391 else
|
|
392 logger->log ("unknown function pointer for checker %qs",
|
|
393 sm.get_name ());
|
|
394 }
|
|
395
|
|
396 /* Purge any state for parms. */
|
|
397 tree iter_param_types = NULL_TREE;
|
|
398 if (fndecl)
|
|
399 iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
|
|
400 for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
|
|
401 {
|
|
402 /* Track expected param type, where available. */
|
|
403 if (iter_param_types)
|
|
404 {
|
|
405 tree param_type = TREE_VALUE (iter_param_types);
|
|
406 gcc_assert (param_type);
|
|
407 iter_param_types = TREE_CHAIN (iter_param_types);
|
|
408
|
|
409 /* Don't purge state if it was passed as a const pointer
|
|
410 e.g. for things like strlen (PTR). */
|
|
411 if (TREE_CODE (param_type) == POINTER_TYPE)
|
|
412 if (TYPE_READONLY (TREE_TYPE (param_type)))
|
|
413 continue;
|
|
414 }
|
|
415 tree parm = gimple_call_arg (call, arg_idx);
|
|
416 svalue_id parm_sid = new_model->get_rvalue (parm, NULL);
|
|
417 set_state (new_model, parm_sid, 0, svalue_id::null ());
|
|
418
|
|
419 /* Also clear sm-state from svalue_ids that are passed via a
|
|
420 pointer. */
|
|
421 if (TREE_CODE (parm) == ADDR_EXPR)
|
|
422 {
|
|
423 tree pointee = TREE_OPERAND (parm, 0);
|
|
424 svalue_id parm_sid = new_model->get_rvalue (pointee, NULL);
|
|
425 set_state (new_model, parm_sid, 0, svalue_id::null ());
|
|
426 }
|
|
427 }
|
|
428
|
|
429 /* Purge any state for any LHS. */
|
|
430 if (tree lhs = gimple_call_lhs (call))
|
|
431 {
|
|
432 svalue_id lhs_sid = new_model->get_rvalue (lhs, NULL);
|
|
433 set_state (new_model, lhs_sid, 0, svalue_id::null ());
|
|
434 }
|
|
435 }
|
|
436
|
|
437 /* Update this map based on MAP. */
|
|
438
|
|
439 void
|
|
440 sm_state_map::remap_svalue_ids (const svalue_id_map &map)
|
|
441 {
|
|
442 map_t tmp_map;
|
|
443
|
|
444 /* Build an intermediate map, using the new sids. */
|
|
445 for (map_t::iterator iter = m_map.begin ();
|
|
446 iter != m_map.end ();
|
|
447 ++iter)
|
|
448 {
|
|
449 svalue_id sid = (*iter).first;
|
|
450 entry_t e = (*iter).second;
|
|
451
|
|
452 map.update (&sid);
|
|
453 map.update (&e.m_origin);
|
|
454 tmp_map.put (sid, e);
|
|
455 }
|
|
456
|
|
457 /* Clear the existing values. */
|
|
458 m_map.empty ();
|
|
459
|
|
460 /* Copy over from intermediate map. */
|
|
461 for (map_t::iterator iter = tmp_map.begin ();
|
|
462 iter != tmp_map.end ();
|
|
463 ++iter)
|
|
464 {
|
|
465 svalue_id sid = (*iter).first;
|
|
466 entry_t e = (*iter).second;
|
|
467
|
|
468 impl_set_state (sid, e.m_state, e.m_origin);
|
|
469 }
|
|
470 }
|
|
471
|
|
472 /* Purge any state for svalue_ids >= FIRST_UNUSED_SID.
|
|
473 If !SM::can_purge_p, then report the state as leaking,
|
|
474 using SM_IDX, CTXT, and MAP.
|
|
475 Return the number of states that were purged. */
|
|
476
|
|
477 int
|
|
478 sm_state_map::on_svalue_purge (const state_machine &sm,
|
|
479 int sm_idx,
|
|
480 svalue_id first_unused_sid,
|
|
481 const svalue_id_map &map,
|
|
482 impl_region_model_context *ctxt)
|
|
483 {
|
|
484 /* TODO: ideally remove the slot directly; for now
|
|
485 do it in two stages. */
|
|
486 auto_vec<svalue_id> to_remove;
|
|
487 for (map_t::iterator iter = m_map.begin ();
|
|
488 iter != m_map.end ();
|
|
489 ++iter)
|
|
490 {
|
|
491 svalue_id dst_sid ((*iter).first);
|
|
492 if (dst_sid.as_int () >= first_unused_sid.as_int ())
|
|
493 {
|
|
494 /* Complain about leaks here. */
|
|
495 entry_t e = (*iter).second;
|
|
496
|
|
497 if (!sm.can_purge_p (e.m_state))
|
|
498 ctxt->on_state_leak (sm, sm_idx, dst_sid, first_unused_sid,
|
|
499 map, e.m_state);
|
|
500
|
|
501 to_remove.safe_push (dst_sid);
|
|
502 }
|
|
503 else if ((*iter).second.m_origin.as_int () >= first_unused_sid.as_int ())
|
|
504 {
|
|
505 /* If the origin svalue is being purged, then reset it to null. */
|
|
506 (*iter).second.m_origin = svalue_id::null ();
|
|
507 }
|
|
508 }
|
|
509
|
|
510 int i;
|
|
511 svalue_id *dst_sid;
|
|
512 FOR_EACH_VEC_ELT (to_remove, i, dst_sid)
|
|
513 m_map.remove (*dst_sid);
|
|
514
|
|
515 return to_remove.length ();
|
|
516 }
|
|
517
|
|
518 /* Set the state of CHILD_SID to that of PARENT_SID. */
|
|
519
|
|
520 void
|
|
521 sm_state_map::on_inherited_svalue (svalue_id parent_sid,
|
|
522 svalue_id child_sid)
|
|
523 {
|
|
524 state_machine::state_t state = get_state (parent_sid);
|
|
525 impl_set_state (child_sid, state, parent_sid);
|
|
526 }
|
|
527
|
|
528 /* Set the state of DST_SID to that of SRC_SID. */
|
|
529
|
|
530 void
|
|
531 sm_state_map::on_cast (svalue_id src_sid,
|
|
532 svalue_id dst_sid)
|
|
533 {
|
|
534 state_machine::state_t state = get_state (src_sid);
|
|
535 impl_set_state (dst_sid, state, get_origin (src_sid));
|
|
536 }
|
|
537
|
|
538 /* Purge state from SID (in response to a call to an unknown function). */
|
|
539
|
|
540 void
|
|
541 sm_state_map::on_unknown_change (svalue_id sid)
|
|
542 {
|
|
543 impl_set_state (sid, (state_machine::state_t)0, svalue_id::null ());
|
|
544 }
|
|
545
|
|
546 /* Assert that this object is sane. */
|
|
547
|
|
548 void
|
|
549 sm_state_map::validate (const state_machine &sm,
|
|
550 int num_svalues) const
|
|
551 {
|
|
552 /* Skip this in a release build. */
|
|
553 #if !CHECKING_P
|
|
554 return;
|
|
555 #endif
|
|
556
|
|
557 for (map_t::iterator iter = m_map.begin ();
|
|
558 iter != m_map.end ();
|
|
559 ++iter)
|
|
560 {
|
|
561 svalue_id sid = (*iter).first;
|
|
562 entry_t e = (*iter).second;
|
|
563
|
|
564 gcc_assert (sid.as_int () < num_svalues);
|
|
565 sm.validate (e.m_state);
|
|
566 gcc_assert (e.m_origin.as_int () < num_svalues);
|
|
567 }
|
|
568 }
|
|
569
|
|
570 /* class program_state. */
|
|
571
|
|
572 /* program_state's ctor. */
|
|
573
|
|
574 program_state::program_state (const extrinsic_state &ext_state)
|
|
575 : m_region_model (new region_model ()),
|
|
576 m_checker_states (ext_state.get_num_checkers ())
|
|
577 {
|
|
578 int num_states = ext_state.get_num_checkers ();
|
|
579 for (int i = 0; i < num_states; i++)
|
|
580 m_checker_states.quick_push (new sm_state_map ());
|
|
581 }
|
|
582
|
|
583 /* program_state's copy ctor. */
|
|
584
|
|
585 program_state::program_state (const program_state &other)
|
|
586 : m_region_model (new region_model (*other.m_region_model)),
|
|
587 m_checker_states (other.m_checker_states.length ())
|
|
588 {
|
|
589 int i;
|
|
590 sm_state_map *smap;
|
|
591 FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
|
|
592 m_checker_states.quick_push (smap->clone ());
|
|
593 }
|
|
594
|
|
595 /* program_state's assignment operator. */
|
|
596
|
|
597 program_state&
|
|
598 program_state::operator= (const program_state &other)
|
|
599 {
|
|
600 delete m_region_model;
|
|
601 m_region_model = new region_model (*other.m_region_model);
|
|
602
|
|
603 int i;
|
|
604 sm_state_map *smap;
|
|
605 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
606 delete smap;
|
|
607 m_checker_states.truncate (0);
|
|
608 gcc_assert (m_checker_states.space (other.m_checker_states.length ()));
|
|
609
|
|
610 FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
|
|
611 m_checker_states.quick_push (smap->clone ());
|
|
612
|
|
613 return *this;
|
|
614 }
|
|
615
|
|
616 #if __cplusplus >= 201103
|
|
617 /* Move constructor for program_state (when building with C++11). */
|
|
618 program_state::program_state (program_state &&other)
|
|
619 : m_region_model (other.m_region_model),
|
|
620 m_checker_states (other.m_checker_states.length ())
|
|
621 {
|
|
622 other.m_region_model = NULL;
|
|
623
|
|
624 int i;
|
|
625 sm_state_map *smap;
|
|
626 FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
|
|
627 m_checker_states.quick_push (smap);
|
|
628 other.m_checker_states.truncate (0);
|
|
629 }
|
|
630 #endif
|
|
631
|
|
632 /* program_state's dtor. */
|
|
633
|
|
634 program_state::~program_state ()
|
|
635 {
|
|
636 delete m_region_model;
|
|
637 }
|
|
638
|
|
639 /* Generate a hash value for this program_state. */
|
|
640
|
|
641 hashval_t
|
|
642 program_state::hash () const
|
|
643 {
|
|
644 hashval_t result = m_region_model->hash ();
|
|
645
|
|
646 int i;
|
|
647 sm_state_map *smap;
|
|
648 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
649 result ^= smap->hash ();
|
|
650 return result;
|
|
651 }
|
|
652
|
|
653 /* Equality operator for program_state.
|
|
654 All parts of the program_state (region model, checker states) must
|
|
655 equal their counterparts in OTHER for the two program_states to be
|
|
656 considered equal. */
|
|
657
|
|
658 bool
|
|
659 program_state::operator== (const program_state &other) const
|
|
660 {
|
|
661 if (!(*m_region_model == *other.m_region_model))
|
|
662 return false;
|
|
663
|
|
664 int i;
|
|
665 sm_state_map *smap;
|
|
666 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
667 if (!(*smap == *other.m_checker_states[i]))
|
|
668 return false;
|
|
669
|
|
670 gcc_checking_assert (hash () == other.hash ());
|
|
671
|
|
672 return true;
|
|
673 }
|
|
674
|
|
675 /* Print a compact representation of this state to PP. */
|
|
676
|
|
677 void
|
|
678 program_state::print (const extrinsic_state &ext_state,
|
|
679 pretty_printer *pp) const
|
|
680 {
|
|
681 pp_printf (pp, "rmodel: ");
|
|
682 m_region_model->print (pp);
|
|
683 pp_newline (pp);
|
|
684
|
|
685 int i;
|
|
686 sm_state_map *smap;
|
|
687 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
688 {
|
|
689 if (!smap->is_empty_p ())
|
|
690 {
|
|
691 pp_printf (pp, "%s: ", ext_state.get_name (i));
|
|
692 smap->print (ext_state.get_sm (i), pp);
|
|
693 pp_newline (pp);
|
|
694 }
|
|
695 }
|
|
696 }
|
|
697
|
|
698 /* Dump a multiline representation of this state to PP. */
|
|
699
|
|
700 void
|
|
701 program_state::dump_to_pp (const extrinsic_state &ext_state,
|
|
702 bool summarize,
|
|
703 pretty_printer *pp) const
|
|
704 {
|
|
705 pp_printf (pp, "rmodel: ");
|
|
706 m_region_model->dump_to_pp (pp, summarize);
|
|
707
|
|
708 int i;
|
|
709 sm_state_map *smap;
|
|
710 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
711 {
|
|
712 if (!smap->is_empty_p ())
|
|
713 {
|
|
714 pp_printf (pp, "%s: ", ext_state.get_name (i));
|
|
715 smap->print (ext_state.get_sm (i), pp);
|
|
716 pp_newline (pp);
|
|
717 }
|
|
718 }
|
|
719 }
|
|
720
|
|
721 /* Dump a multiline representation of this state to OUTF. */
|
|
722
|
|
723 void
|
|
724 program_state::dump_to_file (const extrinsic_state &ext_state,
|
|
725 bool summarize,
|
|
726 FILE *outf) const
|
|
727 {
|
|
728 pretty_printer pp;
|
|
729 pp_format_decoder (&pp) = default_tree_printer;
|
|
730 if (outf == stderr)
|
|
731 pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
732 pp.buffer->stream = outf;
|
|
733 dump_to_pp (ext_state, summarize, &pp);
|
|
734 pp_flush (&pp);
|
|
735 }
|
|
736
|
|
737 /* Dump a multiline representation of this state to stderr. */
|
|
738
|
|
739 DEBUG_FUNCTION void
|
|
740 program_state::dump (const extrinsic_state &ext_state,
|
|
741 bool summarize) const
|
|
742 {
|
|
743 dump_to_file (ext_state, summarize, stderr);
|
|
744 }
|
|
745
|
|
746 /* Determine if following edge SUCC from ENODE is valid within the graph EG
|
|
747 and update this state accordingly in-place.
|
|
748
|
|
749 Return true if the edge can be followed, or false otherwise.
|
|
750
|
|
751 Check for relevant conditionals and switch-values for conditionals
|
|
752 and switch statements, adding the relevant conditions to this state.
|
|
753 Push/pop frames for interprocedural edges and update params/returned
|
|
754 values.
|
|
755
|
|
756 This is the "state" half of exploded_node::on_edge. */
|
|
757
|
|
758 bool
|
|
759 program_state::on_edge (exploded_graph &eg,
|
|
760 const exploded_node &enode,
|
|
761 const superedge *succ,
|
|
762 state_change *change)
|
|
763 {
|
|
764 /* Update state. */
|
|
765 const program_point &point = enode.get_point ();
|
|
766 const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
|
|
767
|
|
768 /* For conditionals and switch statements, add the
|
|
769 relevant conditions (for the specific edge) to new_state;
|
|
770 skip edges for which the resulting constraints
|
|
771 are impossible.
|
|
772 This also updates frame information for call/return superedges.
|
|
773 Adding the relevant conditions for the edge could also trigger
|
|
774 sm-state transitions (e.g. transitions due to ptrs becoming known
|
|
775 to be NULL or non-NULL) */
|
|
776
|
|
777 impl_region_model_context ctxt (eg, &enode,
|
|
778 &enode.get_state (),
|
|
779 this, change,
|
|
780 last_stmt);
|
|
781 if (!m_region_model->maybe_update_for_edge (*succ,
|
|
782 last_stmt,
|
|
783 &ctxt))
|
|
784 {
|
|
785 logger * const logger = eg.get_logger ();
|
|
786 if (logger)
|
|
787 logger->log ("edge to SN: %i is impossible"
|
|
788 " due to region_model constraints",
|
|
789 succ->m_dest->m_index);
|
|
790 return false;
|
|
791 }
|
|
792
|
|
793 return true;
|
|
794 }
|
|
795
|
|
796 /* Generate a simpler version of THIS, discarding state that's no longer
|
|
797 relevant at POINT.
|
|
798 The idea is that we're more likely to be able to consolidate
|
|
799 multiple (point, state) into single exploded_nodes if we discard
|
|
800 irrelevant state (e.g. at the end of functions).
|
|
801
|
|
802 Retain state affected by CHANGE, to make it easier to generate
|
|
803 state_change_events. */
|
|
804
|
|
805 program_state
|
|
806 program_state::prune_for_point (exploded_graph &eg,
|
|
807 const program_point &point,
|
|
808 state_change *change) const
|
|
809 {
|
|
810 logger * const logger = eg.get_logger ();
|
|
811 LOG_SCOPE (logger);
|
|
812
|
|
813 function *fun = point.get_function ();
|
|
814 if (!fun)
|
|
815 return *this;
|
|
816
|
|
817 program_state new_state (*this);
|
|
818
|
|
819 purge_stats stats;
|
|
820
|
|
821 const state_purge_map *pm = eg.get_purge_map ();
|
|
822 if (pm)
|
|
823 {
|
|
824 region_id_set purgeable_ssa_regions (new_state.m_region_model);
|
|
825 region_id frame_rid
|
|
826 = new_state.m_region_model->get_current_frame_id ();
|
|
827 frame_region *frame
|
|
828 = new_state.m_region_model->get_region <frame_region>(frame_rid);
|
|
829
|
|
830 /* TODO: maybe move to a member of region_model? */
|
|
831
|
|
832 auto_vec<tree> ssa_names_to_purge;
|
|
833 for (frame_region::map_t::iterator iter = frame->begin ();
|
|
834 iter != frame->end ();
|
|
835 ++iter)
|
|
836 {
|
|
837 tree var = (*iter).first;
|
|
838 region_id rid = (*iter).second;
|
|
839 if (TREE_CODE (var) == SSA_NAME)
|
|
840 {
|
|
841 const state_purge_per_ssa_name &per_ssa
|
|
842 = pm->get_data_for_ssa_name (var);
|
|
843 if (!per_ssa.needed_at_point_p (point.get_function_point ()))
|
|
844 {
|
|
845 region *region
|
|
846 = new_state.m_region_model->get_region (rid);
|
|
847 svalue_id sid = region->get_value_direct ();
|
|
848 if (!sid.null_p ())
|
|
849 {
|
|
850 if (!new_state.can_purge_p (eg.get_ext_state (), sid))
|
|
851 {
|
|
852 /* (currently only state maps can keep things
|
|
853 alive). */
|
|
854 if (logger)
|
|
855 logger->log ("not purging RID: %i for %qE"
|
|
856 " (used by state map)",
|
|
857 rid.as_int (), var);
|
|
858 continue;
|
|
859 }
|
|
860
|
|
861 /* Don't purge regions containing svalues that
|
|
862 have a change of sm-state, to make it easier to
|
|
863 generate state_change_event messages. */
|
|
864 if (change)
|
|
865 if (change->affects_p (sid))
|
|
866 {
|
|
867 if (logger)
|
|
868 logger->log ("not purging RID: %i for %qE"
|
|
869 " (affected by change)",
|
|
870 rid.as_int (), var);
|
|
871 continue;
|
|
872 }
|
|
873 }
|
|
874 purgeable_ssa_regions.add_region (rid);
|
|
875 ssa_names_to_purge.safe_push (var);
|
|
876 if (logger)
|
|
877 logger->log ("purging RID: %i for %qE", rid.as_int (), var);
|
|
878 /* We also need to remove the region from the map.
|
|
879 We're in mid-traversal, so the removal is done in
|
|
880 unbind below. */
|
|
881 }
|
|
882 }
|
|
883 }
|
|
884
|
|
885 /* Unbind the regions from the frame's map of vars-to-regions. */
|
|
886 unsigned i;
|
|
887 tree var;
|
|
888 FOR_EACH_VEC_ELT (ssa_names_to_purge, i, var)
|
|
889 frame->unbind (var);
|
|
890
|
|
891 /* Purge the regions. Nothing should point to them, and they
|
|
892 should have no children, as they are for SSA names. */
|
|
893 new_state.m_region_model->purge_regions (purgeable_ssa_regions,
|
|
894 &stats,
|
|
895 eg.get_logger ());
|
|
896 }
|
|
897
|
|
898 /* Purge unused svalues. */
|
|
899 // TODO: which enode to use, if any?
|
|
900 impl_region_model_context ctxt (eg, NULL,
|
|
901 this,
|
|
902 &new_state,
|
|
903 change,
|
|
904 NULL);
|
|
905 new_state.m_region_model->purge_unused_svalues (&stats, &ctxt);
|
|
906 if (logger)
|
|
907 {
|
|
908 logger->log ("num svalues purged: %i", stats.m_num_svalues);
|
|
909 logger->log ("num regions purged: %i", stats.m_num_regions);
|
|
910 logger->log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
|
|
911 logger->log ("num constraints purged: %i", stats.m_num_constraints);
|
|
912 logger->log ("num sm map items purged: %i", stats.m_num_client_items);
|
|
913 }
|
|
914
|
|
915 new_state.m_region_model->canonicalize (&ctxt);
|
|
916
|
|
917 return new_state;
|
|
918 }
|
|
919
|
|
920 /* Remap all svalue_ids in this state's m_checker_states according to MAP.
|
|
921 The svalues_ids in the region_model are assumed to already have been
|
|
922 remapped. */
|
|
923
|
|
924 void
|
|
925 program_state::remap_svalue_ids (const svalue_id_map &map)
|
|
926 {
|
|
927 int i;
|
|
928 sm_state_map *smap;
|
|
929 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
930 smap->remap_svalue_ids (map);
|
|
931 }
|
|
932
|
|
933 /* Attempt to return a tree that represents SID, or return NULL_TREE.
|
|
934 Find the first region that stores the value (e.g. a local) and
|
|
935 generate a representative tree for it. */
|
|
936
|
|
937 tree
|
|
938 program_state::get_representative_tree (svalue_id sid) const
|
|
939 {
|
|
940 return m_region_model->get_representative_tree (sid);
|
|
941 }
|
|
942
|
|
943 /* Attempt to merge this state with OTHER, both using EXT_STATE.
|
|
944 Write the result to *OUT.
|
|
945 If the states were merged successfully, return true. */
|
|
946
|
|
947 bool
|
|
948 program_state::can_merge_with_p (const program_state &other,
|
|
949 const extrinsic_state &ext_state,
|
|
950 program_state *out) const
|
|
951 {
|
|
952 gcc_assert (out);
|
|
953
|
|
954 /* TODO: initially I had an early reject here if there
|
|
955 are sm-differences between the states. However, this was
|
|
956 falsely rejecting merger opportunities for states where the
|
|
957 only difference was in svalue_id ordering. */
|
|
958
|
|
959 /* Attempt to merge the region_models. */
|
|
960
|
|
961 svalue_id_merger_mapping sid_mapping (*m_region_model,
|
|
962 *other.m_region_model);
|
|
963 if (!m_region_model->can_merge_with_p (*other.m_region_model,
|
|
964 out->m_region_model,
|
|
965 &sid_mapping))
|
|
966 return false;
|
|
967
|
|
968 /* Copy m_checker_states to result, remapping svalue_ids using
|
|
969 sid_mapping. */
|
|
970 int i;
|
|
971 sm_state_map *smap;
|
|
972 FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
|
|
973 delete smap;
|
|
974 out->m_checker_states.truncate (0);
|
|
975
|
|
976 /* Remap this and other's m_checker_states using sid_mapping.
|
|
977 Only merge states that have equality between the two end-results:
|
|
978 sm-state differences are likely to be interesting to end-users, and
|
|
979 hence are worth exploring as separate paths in the exploded graph. */
|
|
980 FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
|
981 {
|
|
982 sm_state_map *other_smap = other.m_checker_states[i];
|
|
983
|
|
984 /* If clone_with_remapping returns NULL for one of the input smaps,
|
|
985 then it has sm-state for an svalue_id where the svalue_id is
|
|
986 being mapped to svalue_id::null in its sid_mapping, meaning that
|
|
987 the svalue is to be dropped during the merger. We don't want
|
|
988 to lose sm-state during a state merger, so return false for these
|
|
989 cases. */
|
|
990 sm_state_map *remapped_a_smap
|
|
991 = smap->clone_with_remapping (sid_mapping.m_map_from_a_to_m);
|
|
992 if (!remapped_a_smap)
|
|
993 return false;
|
|
994 sm_state_map *remapped_b_smap
|
|
995 = other_smap->clone_with_remapping (sid_mapping.m_map_from_b_to_m);
|
|
996 if (!remapped_b_smap)
|
|
997 {
|
|
998 delete remapped_a_smap;
|
|
999 return false;
|
|
1000 }
|
|
1001
|
|
1002 /* Both states have sm-state for the same values; now ensure that the
|
|
1003 states are equal. */
|
|
1004 if (*remapped_a_smap == *remapped_b_smap)
|
|
1005 {
|
|
1006 out->m_checker_states.safe_push (remapped_a_smap);
|
|
1007 delete remapped_b_smap;
|
|
1008 }
|
|
1009 else
|
|
1010 {
|
|
1011 /* Don't merge if there are sm-state differences. */
|
|
1012 delete remapped_a_smap;
|
|
1013 delete remapped_b_smap;
|
|
1014 return false;
|
|
1015 }
|
|
1016 }
|
|
1017
|
|
1018 impl_region_model_context ctxt (out, NULL, ext_state);
|
|
1019 out->m_region_model->canonicalize (&ctxt);
|
|
1020
|
|
1021 return true;
|
|
1022 }
|
|
1023
|
|
1024 /* Assert that this object is valid. */
|
|
1025
|
|
1026 void
|
|
1027 program_state::validate (const extrinsic_state &ext_state) const
|
|
1028 {
|
|
1029 /* Skip this in a release build. */
|
|
1030 #if !CHECKING_P
|
|
1031 return;
|
|
1032 #endif
|
|
1033
|
|
1034 m_region_model->validate ();
|
|
1035 gcc_assert (m_checker_states.length () == ext_state.get_num_checkers ());
|
|
1036 int sm_idx;
|
|
1037 sm_state_map *smap;
|
|
1038 FOR_EACH_VEC_ELT (m_checker_states, sm_idx, smap)
|
|
1039 {
|
|
1040 const state_machine &sm = ext_state.get_sm (sm_idx);
|
|
1041 smap->validate (sm, m_region_model->get_num_svalues ());
|
|
1042 }
|
|
1043 }
|
|
1044
|
|
1045 /* Dump this sm_change to PP. */
|
|
1046
|
|
1047 void
|
|
1048 state_change::sm_change::dump (pretty_printer *pp,
|
|
1049 const extrinsic_state &ext_state) const
|
|
1050 {
|
|
1051 const state_machine &sm = get_sm (ext_state);
|
|
1052 pp_string (pp, "(");
|
|
1053 m_new_sid.print (pp);
|
|
1054 pp_printf (pp, ": %s: %qs -> %qs)",
|
|
1055 sm.get_name (),
|
|
1056 sm.get_state_name (m_old_state),
|
|
1057 sm.get_state_name (m_new_state));
|
|
1058 }
|
|
1059
|
|
1060 /* Remap all svalue_ids in this change according to MAP. */
|
|
1061
|
|
1062 void
|
|
1063 state_change::sm_change::remap_svalue_ids (const svalue_id_map &map)
|
|
1064 {
|
|
1065 map.update (&m_new_sid);
|
|
1066 }
|
|
1067
|
|
1068 /* Purge any svalue_ids >= FIRST_UNUSED_SID.
|
|
1069 Return the number of states that were purged. */
|
|
1070
|
|
1071 int
|
|
1072 state_change::sm_change::on_svalue_purge (svalue_id first_unused_sid)
|
|
1073 {
|
|
1074 if (m_new_sid.as_int () >= first_unused_sid.as_int ())
|
|
1075 {
|
|
1076 m_new_sid = svalue_id::null ();
|
|
1077 return 1;
|
|
1078 }
|
|
1079
|
|
1080 return 0;
|
|
1081 }
|
|
1082
|
|
1083 /* Assert that this object is sane. */
|
|
1084
|
|
1085 void
|
|
1086 state_change::sm_change::validate (const program_state &new_state,
|
|
1087 const extrinsic_state &ext_state) const
|
|
1088 {
|
|
1089 gcc_assert ((unsigned)m_sm_idx < ext_state.get_num_checkers ());
|
|
1090 const state_machine &sm = ext_state.get_sm (m_sm_idx);
|
|
1091 sm.validate (m_old_state);
|
|
1092 sm.validate (m_new_state);
|
|
1093 m_new_sid.validate (*new_state.m_region_model);
|
|
1094 }
|
|
1095
|
|
1096 /* state_change's ctor. */
|
|
1097
|
|
1098 state_change::state_change ()
|
|
1099 {
|
|
1100 }
|
|
1101
|
|
1102 /* state_change's copy ctor. */
|
|
1103
|
|
1104 state_change::state_change (const state_change &other)
|
|
1105 : m_sm_changes (other.m_sm_changes.length ())
|
|
1106 {
|
|
1107 unsigned i;
|
|
1108 sm_change *change;
|
|
1109 FOR_EACH_VEC_ELT (other.m_sm_changes, i, change)
|
|
1110 m_sm_changes.quick_push (*change);
|
|
1111 }
|
|
1112
|
|
1113 /* Record a state-machine state change. */
|
|
1114
|
|
1115 void
|
|
1116 state_change::add_sm_change (int sm_idx,
|
|
1117 svalue_id new_sid,
|
|
1118 state_machine::state_t old_state,
|
|
1119 state_machine::state_t new_state)
|
|
1120 {
|
|
1121 m_sm_changes.safe_push (sm_change (sm_idx,
|
|
1122 new_sid,
|
|
1123 old_state, new_state));
|
|
1124 }
|
|
1125
|
|
1126 /* Return true if SID (in the new state) was affected by any
|
|
1127 sm-state changes. */
|
|
1128
|
|
1129 bool
|
|
1130 state_change::affects_p (svalue_id sid) const
|
|
1131 {
|
|
1132 unsigned i;
|
|
1133 sm_change *change;
|
|
1134 FOR_EACH_VEC_ELT (m_sm_changes, i, change)
|
|
1135 {
|
|
1136 if (sid == change->m_new_sid)
|
|
1137 return true;
|
|
1138 }
|
|
1139 return false;
|
|
1140 }
|
|
1141
|
|
1142 /* Dump this state_change to PP. */
|
|
1143
|
|
1144 void
|
|
1145 state_change::dump (pretty_printer *pp,
|
|
1146 const extrinsic_state &ext_state) const
|
|
1147 {
|
|
1148 unsigned i;
|
|
1149 sm_change *change;
|
|
1150 FOR_EACH_VEC_ELT (m_sm_changes, i, change)
|
|
1151 {
|
|
1152 if (i > 0)
|
|
1153 pp_string (pp, ", ");
|
|
1154 change->dump (pp, ext_state);
|
|
1155 }
|
|
1156 }
|
|
1157
|
|
1158 /* Dump this state_change to stderr. */
|
|
1159
|
|
1160 void
|
|
1161 state_change::dump (const extrinsic_state &ext_state) const
|
|
1162 {
|
|
1163 pretty_printer pp;
|
|
1164 pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
1165 pp.buffer->stream = stderr;
|
|
1166 dump (&pp, ext_state);
|
|
1167 pp_newline (&pp);
|
|
1168 pp_flush (&pp);
|
|
1169 }
|
|
1170
|
|
1171 /* Remap all svalue_ids in this state_change according to MAP. */
|
|
1172
|
|
1173 void
|
|
1174 state_change::remap_svalue_ids (const svalue_id_map &map)
|
|
1175 {
|
|
1176 unsigned i;
|
|
1177 sm_change *change;
|
|
1178 FOR_EACH_VEC_ELT (m_sm_changes, i, change)
|
|
1179 change->remap_svalue_ids (map);
|
|
1180 }
|
|
1181
|
|
1182 /* Purge any svalue_ids >= FIRST_UNUSED_SID.
|
|
1183 Return the number of states that were purged. */
|
|
1184
|
|
1185 int
|
|
1186 state_change::on_svalue_purge (svalue_id first_unused_sid)
|
|
1187 {
|
|
1188 int result = 0;
|
|
1189 unsigned i;
|
|
1190 sm_change *change;
|
|
1191 FOR_EACH_VEC_ELT (m_sm_changes, i, change)
|
|
1192 result += change->on_svalue_purge (first_unused_sid);
|
|
1193 return result;
|
|
1194 }
|
|
1195
|
|
1196 /* Assert that this object is sane. */
|
|
1197
|
|
1198 void
|
|
1199 state_change::validate (const program_state &new_state,
|
|
1200 const extrinsic_state &ext_state) const
|
|
1201 {
|
|
1202 /* Skip this in a release build. */
|
|
1203 #if !CHECKING_P
|
|
1204 return;
|
|
1205 #endif
|
|
1206 unsigned i;
|
|
1207 sm_change *change;
|
|
1208 FOR_EACH_VEC_ELT (m_sm_changes, i, change)
|
|
1209 change->validate (new_state, ext_state);
|
|
1210 }
|
|
1211
|
|
1212 #if CHECKING_P
|
|
1213
|
|
1214 namespace selftest {
|
|
1215
|
|
1216 /* Tests for sm_state_map. */
|
|
1217
|
|
1218 static void
|
|
1219 test_sm_state_map ()
|
|
1220 {
|
|
1221 tree x = build_global_decl ("x", integer_type_node);
|
|
1222 tree y = build_global_decl ("y", integer_type_node);
|
|
1223 tree z = build_global_decl ("z", integer_type_node);
|
|
1224
|
|
1225 /* Test setting states on svalue_id instances directly. */
|
|
1226 {
|
|
1227 region_model model;
|
|
1228 svalue_id sid_x = model.get_rvalue (x, NULL);
|
|
1229 svalue_id sid_y = model.get_rvalue (y, NULL);
|
|
1230 svalue_id sid_z = model.get_rvalue (z, NULL);
|
|
1231
|
|
1232 sm_state_map map;
|
|
1233 ASSERT_TRUE (map.is_empty_p ());
|
|
1234 ASSERT_EQ (map.get_state (sid_x), 0);
|
|
1235
|
|
1236 map.impl_set_state (sid_x, 42, sid_z);
|
|
1237 ASSERT_EQ (map.get_state (sid_x), 42);
|
|
1238 ASSERT_EQ (map.get_origin (sid_x), sid_z);
|
|
1239 ASSERT_EQ (map.get_state (sid_y), 0);
|
|
1240 ASSERT_FALSE (map.is_empty_p ());
|
|
1241
|
|
1242 map.impl_set_state (sid_y, 0, sid_z);
|
|
1243 ASSERT_EQ (map.get_state (sid_y), 0);
|
|
1244
|
|
1245 map.impl_set_state (sid_x, 0, sid_z);
|
|
1246 ASSERT_EQ (map.get_state (sid_x), 0);
|
|
1247 ASSERT_TRUE (map.is_empty_p ());
|
|
1248 }
|
|
1249
|
|
1250 /* Test setting states via equivalence classes. */
|
|
1251 {
|
|
1252 region_model model;
|
|
1253 svalue_id sid_x = model.get_rvalue (x, NULL);
|
|
1254 svalue_id sid_y = model.get_rvalue (y, NULL);
|
|
1255 svalue_id sid_z = model.get_rvalue (z, NULL);
|
|
1256
|
|
1257 sm_state_map map;
|
|
1258 ASSERT_TRUE (map.is_empty_p ());
|
|
1259 ASSERT_EQ (map.get_state (sid_x), 0);
|
|
1260 ASSERT_EQ (map.get_state (sid_y), 0);
|
|
1261
|
|
1262 model.add_constraint (x, EQ_EXPR, y, NULL);
|
|
1263
|
|
1264 /* Setting x to a state should also update y, as they
|
|
1265 are in the same equivalence class. */
|
|
1266 map.set_state (&model, sid_x, 5, sid_z);
|
|
1267 ASSERT_EQ (map.get_state (sid_x), 5);
|
|
1268 ASSERT_EQ (map.get_state (sid_y), 5);
|
|
1269 ASSERT_EQ (map.get_origin (sid_x), sid_z);
|
|
1270 ASSERT_EQ (map.get_origin (sid_y), sid_z);
|
|
1271 }
|
|
1272
|
|
1273 /* Test equality and hashing. */
|
|
1274 {
|
|
1275 region_model model;
|
|
1276 svalue_id sid_y = model.get_rvalue (y, NULL);
|
|
1277 svalue_id sid_z = model.get_rvalue (z, NULL);
|
|
1278
|
|
1279 sm_state_map map0;
|
|
1280 sm_state_map map1;
|
|
1281 sm_state_map map2;
|
|
1282
|
|
1283 ASSERT_EQ (map0.hash (), map1.hash ());
|
|
1284 ASSERT_EQ (map0, map1);
|
|
1285
|
|
1286 map1.impl_set_state (sid_y, 5, sid_z);
|
|
1287 ASSERT_NE (map0.hash (), map1.hash ());
|
|
1288 ASSERT_NE (map0, map1);
|
|
1289
|
|
1290 /* Make the same change to map2. */
|
|
1291 map2.impl_set_state (sid_y, 5, sid_z);
|
|
1292 ASSERT_EQ (map1.hash (), map2.hash ());
|
|
1293 ASSERT_EQ (map1, map2);
|
|
1294 }
|
|
1295
|
|
1296 /* Equality and hashing shouldn't depend on ordering. */
|
|
1297 {
|
|
1298 sm_state_map map0;
|
|
1299 sm_state_map map1;
|
|
1300 sm_state_map map2;
|
|
1301
|
|
1302 ASSERT_EQ (map0.hash (), map1.hash ());
|
|
1303 ASSERT_EQ (map0, map1);
|
|
1304
|
|
1305 map1.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
|
|
1306 map1.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
|
|
1307 map1.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
|
|
1308 map1.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
|
|
1309
|
|
1310 map2.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
|
|
1311 map2.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
|
|
1312 map2.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
|
|
1313 map2.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
|
|
1314
|
|
1315 ASSERT_EQ (map1.hash (), map2.hash ());
|
|
1316 ASSERT_EQ (map1, map2);
|
|
1317 }
|
|
1318
|
|
1319 /* Test sm_state_map::remap_svalue_ids. */
|
|
1320 {
|
|
1321 sm_state_map map;
|
|
1322 svalue_id sid_0 = svalue_id::from_int (0);
|
|
1323 svalue_id sid_1 = svalue_id::from_int (1);
|
|
1324 svalue_id sid_2 = svalue_id::from_int (2);
|
|
1325
|
|
1326 map.impl_set_state (sid_0, 42, sid_2);
|
|
1327 ASSERT_EQ (map.get_state (sid_0), 42);
|
|
1328 ASSERT_EQ (map.get_origin (sid_0), sid_2);
|
|
1329 ASSERT_EQ (map.get_state (sid_1), 0);
|
|
1330 ASSERT_EQ (map.get_state (sid_2), 0);
|
|
1331
|
|
1332 /* Apply a remapping to the IDs. */
|
|
1333 svalue_id_map remapping (3);
|
|
1334 remapping.put (sid_0, sid_1);
|
|
1335 remapping.put (sid_1, sid_2);
|
|
1336 remapping.put (sid_2, sid_0);
|
|
1337 map.remap_svalue_ids (remapping);
|
|
1338
|
|
1339 /* Verify that the IDs have been remapped. */
|
|
1340 ASSERT_EQ (map.get_state (sid_1), 42);
|
|
1341 ASSERT_EQ (map.get_origin (sid_1), sid_0);
|
|
1342 ASSERT_EQ (map.get_state (sid_2), 0);
|
|
1343 ASSERT_EQ (map.get_state (sid_0), 0);
|
|
1344 }
|
|
1345
|
|
1346 // TODO: coverage for purging
|
|
1347 }
|
|
1348
|
|
1349 /* Verify that program_states with identical sm-state can be merged,
|
|
1350 and that the merged program_state preserves the sm-state. */
|
|
1351
|
|
1352 static void
|
|
1353 test_program_state_merging ()
|
|
1354 {
|
|
1355 /* Create a program_state for a global ptr "p" that has
|
|
1356 malloc sm-state, pointing to a region on the heap. */
|
|
1357 tree p = build_global_decl ("p", ptr_type_node);
|
|
1358
|
|
1359 auto_delete_vec <state_machine> checkers;
|
|
1360 checkers.safe_push (make_malloc_state_machine (NULL));
|
|
1361 extrinsic_state ext_state (checkers);
|
|
1362
|
|
1363 program_state s0 (ext_state);
|
|
1364 impl_region_model_context ctxt (&s0, NULL, ext_state);
|
|
1365
|
|
1366 region_model *model0 = s0.m_region_model;
|
|
1367 region_id new_rid = model0->add_new_malloc_region ();
|
|
1368 svalue_id ptr_sid
|
|
1369 = model0->get_or_create_ptr_svalue (ptr_type_node, new_rid);
|
|
1370 model0->set_value (model0->get_lvalue (p, &ctxt),
|
|
1371 ptr_sid, &ctxt);
|
|
1372 sm_state_map *smap = s0.m_checker_states[0];
|
|
1373 const state_machine::state_t TEST_STATE = 3;
|
|
1374 smap->impl_set_state (ptr_sid, TEST_STATE, svalue_id::null ());
|
|
1375 ASSERT_EQ (smap->get_state (ptr_sid), TEST_STATE);
|
|
1376
|
|
1377 model0->canonicalize (&ctxt);
|
|
1378
|
|
1379 /* Verify that canonicalization preserves sm-state. */
|
|
1380 ASSERT_EQ (smap->get_state (model0->get_rvalue (p, NULL)), TEST_STATE);
|
|
1381
|
|
1382 /* Make a copy of the program_state. */
|
|
1383 program_state s1 (s0);
|
|
1384 ASSERT_EQ (s0, s1);
|
|
1385
|
|
1386 /* We have two identical states with "p" pointing to a heap region
|
|
1387 with the given sm-state.
|
|
1388 They ought to be mergeable, preserving the sm-state. */
|
|
1389 program_state merged (ext_state);
|
|
1390 ASSERT_TRUE (s0.can_merge_with_p (s1, ext_state, &merged));
|
|
1391 merged.validate (ext_state);
|
|
1392
|
|
1393 /* Verify that the merged state has the sm-state for "p". */
|
|
1394 region_model *merged_model = merged.m_region_model;
|
|
1395 sm_state_map *merged_smap = merged.m_checker_states[0];
|
|
1396 ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
|
|
1397 TEST_STATE);
|
|
1398
|
|
1399 /* Try canonicalizing. */
|
|
1400 impl_region_model_context merged_ctxt (&merged, NULL, ext_state);
|
|
1401 merged.m_region_model->canonicalize (&merged_ctxt);
|
|
1402 merged.validate (ext_state);
|
|
1403
|
|
1404 /* Verify that the merged state still has the sm-state for "p". */
|
|
1405 ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
|
|
1406 TEST_STATE);
|
|
1407
|
|
1408 /* After canonicalization, we ought to have equality with the inputs. */
|
|
1409 ASSERT_EQ (s0, merged);
|
|
1410 }
|
|
1411
|
|
1412 /* Verify that program_states with different global-state in an sm-state
|
|
1413 can't be merged. */
|
|
1414
|
|
1415 static void
|
|
1416 test_program_state_merging_2 ()
|
|
1417 {
|
|
1418 auto_delete_vec <state_machine> checkers;
|
|
1419 checkers.safe_push (make_signal_state_machine (NULL));
|
|
1420 extrinsic_state ext_state (checkers);
|
|
1421
|
|
1422 program_state s0 (ext_state);
|
|
1423 {
|
|
1424 sm_state_map *smap0 = s0.m_checker_states[0];
|
|
1425 const state_machine::state_t TEST_STATE_0 = 0;
|
|
1426 smap0->set_global_state (TEST_STATE_0);
|
|
1427 ASSERT_EQ (smap0->get_global_state (), TEST_STATE_0);
|
|
1428 }
|
|
1429
|
|
1430 program_state s1 (ext_state);
|
|
1431 {
|
|
1432 sm_state_map *smap1 = s1.m_checker_states[0];
|
|
1433 const state_machine::state_t TEST_STATE_1 = 1;
|
|
1434 smap1->set_global_state (TEST_STATE_1);
|
|
1435 ASSERT_EQ (smap1->get_global_state (), TEST_STATE_1);
|
|
1436 }
|
|
1437
|
|
1438 ASSERT_NE (s0, s1);
|
|
1439
|
|
1440 /* They ought to not be mergeable. */
|
|
1441 program_state merged (ext_state);
|
|
1442 ASSERT_FALSE (s0.can_merge_with_p (s1, ext_state, &merged));
|
|
1443 }
|
|
1444
|
|
1445 /* Run all of the selftests within this file. */
|
|
1446
|
|
1447 void
|
|
1448 analyzer_program_state_cc_tests ()
|
|
1449 {
|
|
1450 test_sm_state_map ();
|
|
1451 test_program_state_merging ();
|
|
1452 test_program_state_merging_2 ();
|
|
1453 }
|
|
1454
|
|
1455 } // namespace selftest
|
|
1456
|
|
1457 #endif /* CHECKING_P */
|
|
1458
|
|
1459 } // namespace ana
|
|
1460
|
|
1461 #endif /* #if ENABLE_ANALYZER */
|