1 /* Classes for representing locations within the program.
2 Copyright (C) 2019-2022 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 "gimple-pretty-print.h"
26 #include "gcc-rich-location.h"
27 #include "json.h"
28 #include "analyzer/call-string.h"
29 #include "ordered-hash-map.h"
30 #include "options.h"
31 #include "cgraph.h"
32 #include "function.h"
33 #include "cfg.h"
34 #include "basic-block.h"
35 #include "gimple.h"
36 #include "gimple-iterator.h"
37 #include "digraph.h"
38 #include "analyzer/analyzer.h"
39 #include "analyzer/analyzer-logging.h"
40 #include "analyzer/supergraph.h"
41 #include "analyzer/program-point.h"
42 #include "sbitmap.h"
43 #include "bitmap.h"
44 #include "tristate.h"
45 #include "selftest.h"
46 #include "analyzer/store.h"
47 #include "analyzer/region-model.h"
48 #include "analyzer/sm.h"
49 #include "analyzer/program-state.h"
50 #include "alloc-pool.h"
51 #include "fibonacci_heap.h"
52 #include "diagnostic-event-id.h"
53 #include "analyzer/pending-diagnostic.h"
54 #include "analyzer/diagnostic-manager.h"
55 #include "shortest-paths.h"
56 #include "analyzer/exploded-graph.h"
57 #include "analyzer/analysis-plan.h"
58
59 #if ENABLE_ANALYZER
60
61 namespace ana {
62
63 /* Get a string for PK. */
64
65 const char *
point_kind_to_string(enum point_kind pk)66 point_kind_to_string (enum point_kind pk)
67 {
68 switch (pk)
69 {
70 default:
71 gcc_unreachable ();
72 case PK_ORIGIN:
73 return "PK_ORIGIN";
74 case PK_BEFORE_SUPERNODE:
75 return "PK_BEFORE_SUPERNODE";
76 case PK_BEFORE_STMT:
77 return "PK_BEFORE_STMT";
78 case PK_AFTER_SUPERNODE:
79 return "PK_AFTER_SUPERNODE";
80 case PK_EMPTY:
81 return "PK_EMPTY";
82 case PK_DELETED:
83 return "PK_DELETED";
84 }
85 }
86
87 /* class function_point. */
88
function_point(const supernode * supernode,const superedge * from_edge,unsigned stmt_idx,enum point_kind kind)89 function_point::function_point (const supernode *supernode,
90 const superedge *from_edge,
91 unsigned stmt_idx,
92 enum point_kind kind)
93 : m_supernode (supernode), m_from_edge (from_edge),
94 m_stmt_idx (stmt_idx), m_kind (kind)
95 {
96 if (from_edge)
97 {
98 gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
99 gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
100 }
101 if (stmt_idx)
102 gcc_checking_assert (m_kind == PK_BEFORE_STMT);
103 }
104
105 /* Print this function_point to PP. */
106
107 void
print(pretty_printer * pp,const format & f) const108 function_point::print (pretty_printer *pp, const format &f) const
109 {
110 switch (get_kind ())
111 {
112 default:
113 gcc_unreachable ();
114
115 case PK_ORIGIN:
116 pp_printf (pp, "origin");
117 if (f.m_newlines)
118 pp_newline (pp);
119 break;
120
121 case PK_BEFORE_SUPERNODE:
122 {
123 if (m_from_edge)
124 {
125 if (basic_block bb = m_from_edge->m_src->m_bb)
126 pp_printf (pp, "before SN: %i (from SN: %i (bb: %i))",
127 m_supernode->m_index, m_from_edge->m_src->m_index,
128 bb->index);
129 else
130 pp_printf (pp, "before SN: %i (from SN: %i)",
131 m_supernode->m_index, m_from_edge->m_src->m_index);
132 }
133 else
134 pp_printf (pp, "before SN: %i (NULL from-edge)",
135 m_supernode->m_index);
136 f.spacer (pp);
137 for (gphi_iterator gpi
138 = const_cast<supernode *>(get_supernode ())->start_phis ();
139 !gsi_end_p (gpi); gsi_next (&gpi))
140 {
141 const gphi *phi = gpi.phi ();
142 pp_gimple_stmt_1 (pp, phi, 0, (dump_flags_t)0);
143 }
144 }
145 break;
146
147 case PK_BEFORE_STMT:
148 pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
149 m_stmt_idx);
150 f.spacer (pp);
151 pp_gimple_stmt_1 (pp, get_stmt (), 0, (dump_flags_t)0);
152 if (f.m_newlines)
153 {
154 pp_newline (pp);
155 print_source_line (pp);
156 }
157 break;
158
159 case PK_AFTER_SUPERNODE:
160 pp_printf (pp, "after SN: %i", m_supernode->m_index);
161 if (f.m_newlines)
162 pp_newline (pp);
163 break;
164 }
165 }
166
167 /* Generate a hash value for this function_point. */
168
169 hashval_t
hash() const170 function_point::hash () const
171 {
172 inchash::hash hstate;
173 if (m_supernode)
174 hstate.add_int (m_supernode->m_index);
175 hstate.add_ptr (m_from_edge);
176 hstate.add_int (m_stmt_idx);
177 hstate.add_int (m_kind);
178 return hstate.end ();
179 }
180
181 /* Get the function at this point, if any. */
182
183 function *
get_function() const184 function_point::get_function () const
185 {
186 if (m_supernode)
187 return m_supernode->m_fun;
188 else
189 return NULL;
190 }
191
192 /* Get the gimple stmt for this function_point, if any. */
193
194 const gimple *
get_stmt() const195 function_point::get_stmt () const
196 {
197 if (m_kind == PK_BEFORE_STMT)
198 return m_supernode->m_stmts[m_stmt_idx];
199 else if (m_kind == PK_AFTER_SUPERNODE)
200 return m_supernode->get_last_stmt ();
201 else
202 return NULL;
203 }
204
205 /* Get a location for this function_point, if any. */
206
207 location_t
get_location() const208 function_point::get_location () const
209 {
210 const gimple *stmt = get_stmt ();
211 if (stmt)
212 return stmt->location;
213 if (m_kind == PK_BEFORE_SUPERNODE)
214 return m_supernode->get_start_location ();
215 else if (m_kind == PK_AFTER_SUPERNODE)
216 return m_supernode->get_end_location ();
217 else
218 return UNKNOWN_LOCATION;
219 }
220
221 /* Return true if this function_point is a before_stmt for
222 the final stmt in its supernode. */
223
224 bool
final_stmt_p() const225 function_point::final_stmt_p () const
226 {
227 if (m_kind != PK_BEFORE_STMT)
228 return false;
229 return m_stmt_idx == get_supernode ()->m_stmts.length () - 1;
230 }
231
232 /* Create a function_point representing the entrypoint of function FUN. */
233
234 function_point
from_function_entry(const supergraph & sg,function * fun)235 function_point::from_function_entry (const supergraph &sg, function *fun)
236 {
237 return before_supernode (sg.get_node_for_function_entry (fun), NULL);
238 }
239
240 /* Create a function_point representing entering supernode SUPERNODE,
241 having reached it via FROM_EDGE (which could be NULL). */
242
243 function_point
before_supernode(const supernode * supernode,const superedge * from_edge)244 function_point::before_supernode (const supernode *supernode,
245 const superedge *from_edge)
246 {
247 if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
248 from_edge = NULL;
249 return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
250 }
251
252 /* A subclass of diagnostic_context for use by
253 program_point::print_source_line. */
254
255 class debug_diagnostic_context : public diagnostic_context
256 {
257 public:
debug_diagnostic_context()258 debug_diagnostic_context ()
259 {
260 diagnostic_initialize (this, 0);
261 show_line_numbers_p = true;
262 show_caret = true;
263 }
~debug_diagnostic_context()264 ~debug_diagnostic_context ()
265 {
266 diagnostic_finish (this);
267 }
268 };
269
270 /* Print the source line (if any) for this function_point to PP. */
271
272 void
print_source_line(pretty_printer * pp) const273 function_point::print_source_line (pretty_printer *pp) const
274 {
275 const gimple *stmt = get_stmt ();
276 if (!stmt)
277 return;
278 // TODO: monospace font
279 debug_diagnostic_context tmp_dc;
280 gcc_rich_location richloc (stmt->location);
281 diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
282 pp_string (pp, pp_formatted_text (tmp_dc.printer));
283 }
284
285 /* class program_point. */
286
287 /* Print this program_point to PP. */
288
289 void
print(pretty_printer * pp,const format & f) const290 program_point::print (pretty_printer *pp, const format &f) const
291 {
292 pp_string (pp, "callstring: ");
293 m_call_string.print (pp);
294 f.spacer (pp);
295
296 m_function_point.print (pp, f);
297 }
298
299 /* Dump this point to stderr. */
300
301 DEBUG_FUNCTION void
dump() const302 program_point::dump () const
303 {
304 pretty_printer pp;
305 pp_show_color (&pp) = pp_show_color (global_dc->printer);
306 pp.buffer->stream = stderr;
307 print (&pp, format (true));
308 pp_flush (&pp);
309 }
310
311 /* Return a new json::object of the form
312 {"kind" : str,
313 "snode_idx" : int (optional), the index of the supernode,
314 "from_edge_snode_idx" : int (only for kind=='PK_BEFORE_SUPERNODE'),
315 "stmt_idx": int (only for kind=='PK_BEFORE_STMT',
316 "call_string": object for the call_string}. */
317
318 json::object *
to_json() const319 program_point::to_json () const
320 {
321 json::object *point_obj = new json::object ();
322
323 point_obj->set ("kind",
324 new json::string (point_kind_to_string (get_kind ())));
325
326 if (get_supernode ())
327 point_obj->set ("snode_idx",
328 new json::integer_number (get_supernode ()->m_index));
329
330 switch (get_kind ())
331 {
332 default: break;
333 case PK_BEFORE_SUPERNODE:
334 if (const superedge *sedge = get_from_edge ())
335 point_obj->set ("from_edge_snode_idx",
336 new json::integer_number (sedge->m_src->m_index));
337 break;
338 case PK_BEFORE_STMT:
339 point_obj->set ("stmt_idx", new json::integer_number (get_stmt_idx ()));
340 break;
341 }
342
343 point_obj->set ("call_string", m_call_string.to_json ());
344
345 return point_obj;
346 }
347
348 /* Update the callstack to represent a call from caller to callee.
349
350 Generally used to push a custom call to a perticular program point
351 where we don't have a superedge representing the call. */
352 void
push_to_call_stack(const supernode * caller,const supernode * callee)353 program_point::push_to_call_stack (const supernode *caller,
354 const supernode *callee)
355 {
356 m_call_string.push_call (callee, caller);
357 }
358
359 /* Pop the topmost call from the current callstack. */
360 void
pop_from_call_stack()361 program_point::pop_from_call_stack ()
362 {
363 m_call_string.pop ();
364 }
365
366 /* Generate a hash value for this program_point. */
367
368 hashval_t
hash() const369 program_point::hash () const
370 {
371 inchash::hash hstate;
372 hstate.merge_hash (m_function_point.hash ());
373 hstate.merge_hash (m_call_string.hash ());
374 return hstate.end ();
375 }
376
377 /* Get the function * at DEPTH within the call stack. */
378
379 function *
get_function_at_depth(unsigned depth) const380 program_point::get_function_at_depth (unsigned depth) const
381 {
382 gcc_assert (depth <= m_call_string.length ());
383 if (depth == m_call_string.length ())
384 return m_function_point.get_function ();
385 else
386 return m_call_string[depth].get_caller_function ();
387 }
388
389 /* Assert that this object is sane. */
390
391 void
validate() const392 program_point::validate () const
393 {
394 /* Skip this in a release build. */
395 #if !CHECKING_P
396 return;
397 #endif
398
399 m_call_string.validate ();
400 /* The "callee" of the final entry in the callstring should be the
401 function of the m_function_point. */
402 if (m_call_string.length () > 0)
403 gcc_assert (m_call_string[m_call_string.length () - 1].get_callee_function ()
404 == get_function ());
405 }
406
407 /* Check to see if SUCC is a valid edge to take (ensuring that we have
408 interprocedurally valid paths in the exploded graph, and enforcing
409 recursion limits).
410
411 Update the call string if SUCC is a call or a return.
412
413 Return true if SUCC can be taken, or false otherwise.
414
415 This is the "point" half of exploded_node::on_edge. */
416
417 bool
on_edge(exploded_graph & eg,const superedge * succ)418 program_point::on_edge (exploded_graph &eg,
419 const superedge *succ)
420 {
421 logger * const logger = eg.get_logger ();
422 LOG_FUNC (logger);
423 switch (succ->m_kind)
424 {
425 case SUPEREDGE_CFG_EDGE:
426 {
427 const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (succ);
428
429 /* Reject abnormal edges; we special-case setjmp/longjmp. */
430 if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
431 return false;
432 }
433 break;
434
435 case SUPEREDGE_CALL:
436 {
437 const call_superedge *call_sedge = as_a <const call_superedge *> (succ);
438
439 if (eg.get_analysis_plan ().use_summary_p (call_sedge->m_cedge))
440 {
441 if (logger)
442 logger->log ("rejecting call edge: using summary instead");
443 return false;
444 }
445
446 /* Add the callsite to the call string. */
447 m_call_string.push_call (eg.get_supergraph (), call_sedge);
448
449 /* Impose a maximum recursion depth and don't analyze paths
450 that exceed it further.
451 This is something of a blunt workaround, but it only
452 applies to recursion (and mutual recursion), not to
453 general call stacks. */
454 if (m_call_string.calc_recursion_depth ()
455 > param_analyzer_max_recursion_depth)
456 {
457 if (logger)
458 logger->log ("rejecting call edge: recursion limit exceeded");
459 // TODO: issue a sorry for this?
460 return false;
461 }
462 }
463 break;
464
465 case SUPEREDGE_RETURN:
466 {
467 /* Require that we return to the call site in the call string. */
468 if (m_call_string.empty_p ())
469 {
470 if (logger)
471 logger->log ("rejecting return edge: empty call string");
472 return false;
473 }
474 const call_string::element_t top_of_stack = m_call_string.pop ();
475 call_string::element_t current_call_string_element (succ->m_dest,
476 succ->m_src);
477 if (top_of_stack != current_call_string_element)
478 {
479 if (logger)
480 logger->log ("rejecting return edge: return to wrong callsite");
481 return false;
482 }
483 }
484 break;
485
486 case SUPEREDGE_INTRAPROCEDURAL_CALL:
487 {
488 const callgraph_superedge *cg_sedge
489 = as_a <const callgraph_superedge *> (succ);
490 /* Consider turning this edge into a use of an
491 interprocedural summary. */
492 if (eg.get_analysis_plan ().use_summary_p (cg_sedge->m_cedge))
493 {
494 if (logger)
495 logger->log ("using function summary for %qE in %qE",
496 cg_sedge->get_callee_decl (),
497 cg_sedge->get_caller_decl ());
498 return true;
499 }
500 else
501 {
502 /* Otherwise, we ignore these edges */
503 if (logger)
504 logger->log ("rejecting interprocedural edge");
505 return false;
506 }
507 }
508 }
509
510 return true;
511 }
512
513 /* Comparator for program points within the same supernode,
514 for implementing worklist::key_t comparison operators.
515 Return negative if POINT_A is before POINT_B
516 Return positive if POINT_A is after POINT_B
517 Return 0 if they are equal. */
518
519 int
cmp_within_supernode_1(const function_point & point_a,const function_point & point_b)520 function_point::cmp_within_supernode_1 (const function_point &point_a,
521 const function_point &point_b)
522 {
523 gcc_assert (point_a.get_supernode () == point_b.get_supernode ());
524
525 switch (point_a.m_kind)
526 {
527 default:
528 gcc_unreachable ();
529 case PK_BEFORE_SUPERNODE:
530 switch (point_b.m_kind)
531 {
532 default:
533 gcc_unreachable ();
534 case PK_BEFORE_SUPERNODE:
535 {
536 int a_src_idx = -1;
537 int b_src_idx = -1;
538 if (point_a.m_from_edge)
539 a_src_idx = point_a.m_from_edge->m_src->m_index;
540 if (point_b.m_from_edge)
541 b_src_idx = point_b.m_from_edge->m_src->m_index;
542 return a_src_idx - b_src_idx;
543 }
544 break;
545
546 case PK_BEFORE_STMT:
547 case PK_AFTER_SUPERNODE:
548 return -1;
549 }
550 break;
551 case PK_BEFORE_STMT:
552 switch (point_b.m_kind)
553 {
554 default:
555 gcc_unreachable ();
556 case PK_BEFORE_SUPERNODE:
557 return 1;
558
559 case PK_BEFORE_STMT:
560 return point_a.m_stmt_idx - point_b.m_stmt_idx;
561
562 case PK_AFTER_SUPERNODE:
563 return -1;
564 }
565 break;
566 case PK_AFTER_SUPERNODE:
567 switch (point_b.m_kind)
568 {
569 default:
570 gcc_unreachable ();
571 case PK_BEFORE_SUPERNODE:
572 case PK_BEFORE_STMT:
573 return 1;
574
575 case PK_AFTER_SUPERNODE:
576 return 0;
577 }
578 break;
579 }
580 }
581
582 /* Comparator for program points within the same supernode,
583 for implementing worklist::key_t comparison operators.
584 Return negative if POINT_A is before POINT_B
585 Return positive if POINT_A is after POINT_B
586 Return 0 if they are equal. */
587
588 int
cmp_within_supernode(const function_point & point_a,const function_point & point_b)589 function_point::cmp_within_supernode (const function_point &point_a,
590 const function_point &point_b)
591 {
592 int result = cmp_within_supernode_1 (point_a, point_b);
593
594 /* Check that the ordering is symmetric */
595 #if CHECKING_P
596 int reversed = cmp_within_supernode_1 (point_b, point_a);
597 gcc_assert (reversed == -result);
598 #endif
599
600 return result;
601 }
602
603 /* Comparator for imposing an order on function_points. */
604
605 int
cmp(const function_point & point_a,const function_point & point_b)606 function_point::cmp (const function_point &point_a,
607 const function_point &point_b)
608 {
609 int idx_a = point_a.m_supernode ? point_a.m_supernode->m_index : -1;
610 int idx_b = point_b.m_supernode ? point_b.m_supernode->m_index : -1;
611 if (int cmp_idx = idx_a - idx_b)
612 return cmp_idx;
613 gcc_assert (point_a.m_supernode == point_b.m_supernode);
614 if (point_a.m_supernode)
615 return cmp_within_supernode (point_a, point_b);
616 else
617 return 0;
618 }
619
620 /* Comparator for use by vec<function_point>::qsort. */
621
622 int
cmp_ptr(const void * p1,const void * p2)623 function_point::cmp_ptr (const void *p1, const void *p2)
624 {
625 const function_point *fp1 = (const function_point *)p1;
626 const function_point *fp2 = (const function_point *)p2;
627 return cmp (*fp1, *fp2);
628 }
629
630 /* For PK_BEFORE_STMT, go to next stmt (or to PK_AFTER_SUPERNODE). */
631
632 void
next_stmt()633 function_point::next_stmt ()
634 {
635 gcc_assert (m_kind == PK_BEFORE_STMT);
636 if (++m_stmt_idx == m_supernode->m_stmts.length ())
637 {
638 m_kind = PK_AFTER_SUPERNODE;
639 m_stmt_idx = 0;
640 }
641 }
642
643 /* For those function points for which there is a uniquely-defined
644 successor, return it. */
645
646 function_point
get_next() const647 function_point::get_next () const
648 {
649 switch (get_kind ())
650 {
651 default:
652 gcc_unreachable ();
653 case PK_ORIGIN:
654 case PK_AFTER_SUPERNODE:
655 gcc_unreachable (); /* Not uniquely defined. */
656 case PK_BEFORE_SUPERNODE:
657 if (get_supernode ()->m_stmts.length () > 0)
658 return before_stmt (get_supernode (), 0);
659 else
660 return after_supernode (get_supernode ());
661 case PK_BEFORE_STMT:
662 {
663 unsigned next_idx = get_stmt_idx () + 1;
664 if (next_idx < get_supernode ()->m_stmts.length ())
665 return before_stmt (get_supernode (), next_idx);
666 else
667 return after_supernode (get_supernode ());
668 }
669 }
670 }
671
672 /* For those program points for which there is a uniquely-defined
673 successor, return it. */
674
675 program_point
get_next() const676 program_point::get_next () const
677 {
678 switch (m_function_point.get_kind ())
679 {
680 default:
681 gcc_unreachable ();
682 case PK_ORIGIN:
683 case PK_AFTER_SUPERNODE:
684 gcc_unreachable (); /* Not uniquely defined. */
685 case PK_BEFORE_SUPERNODE:
686 if (get_supernode ()->m_stmts.length () > 0)
687 return before_stmt (get_supernode (), 0, get_call_string ());
688 else
689 return after_supernode (get_supernode (), get_call_string ());
690 case PK_BEFORE_STMT:
691 {
692 unsigned next_idx = get_stmt_idx () + 1;
693 if (next_idx < get_supernode ()->m_stmts.length ())
694 return before_stmt (get_supernode (), next_idx, get_call_string ());
695 else
696 return after_supernode (get_supernode (), get_call_string ());
697 }
698 }
699 }
700
701 #if CHECKING_P
702
703 namespace selftest {
704
705 /* Verify that function_point::operator== works as expected. */
706
707 static void
test_function_point_equality()708 test_function_point_equality ()
709 {
710 const supernode *snode = NULL;
711
712 function_point a = function_point (snode, NULL, 0,
713 PK_BEFORE_SUPERNODE);
714 function_point b = function_point::before_supernode (snode, NULL);
715 ASSERT_EQ (a, b);
716 }
717
718 /* Verify that function_point::cmp_within_supernode works as expected. */
719
720 static void
test_function_point_ordering()721 test_function_point_ordering ()
722 {
723 const supernode *snode = NULL;
724 const call_string call_string;
725
726 /* Populate an array with various points within the same
727 snode, in order. */
728 auto_vec<function_point> points;
729 points.safe_push (function_point::before_supernode (snode, NULL));
730 points.safe_push (function_point::before_stmt (snode, 0));
731 points.safe_push (function_point::before_stmt (snode, 1));
732 points.safe_push (function_point::after_supernode (snode));
733
734 /* Check all pairs. */
735 unsigned i;
736 function_point *point_a;
737 FOR_EACH_VEC_ELT (points, i, point_a)
738 {
739 unsigned j;
740 function_point *point_b;
741 FOR_EACH_VEC_ELT (points, j, point_b)
742 {
743 int cmp = function_point::cmp_within_supernode (*point_a, *point_b);
744 if (i == j)
745 ASSERT_EQ (cmp, 0);
746 if (i < j)
747 ASSERT_TRUE (cmp < 0);
748 if (i > j)
749 ASSERT_TRUE (cmp > 0);
750 }
751 }
752 }
753
754 /* Verify that program_point::operator== works as expected. */
755
756 static void
test_program_point_equality()757 test_program_point_equality ()
758 {
759 const supernode *snode = NULL;
760
761 const call_string cs;
762
763 program_point a = program_point::before_supernode (snode, NULL,
764 cs);
765
766 program_point b = program_point::before_supernode (snode, NULL,
767 cs);
768
769 ASSERT_EQ (a, b);
770 // TODO: verify with non-empty callstrings, with different edges
771 }
772
773 /* Run all of the selftests within this file. */
774
775 void
analyzer_program_point_cc_tests()776 analyzer_program_point_cc_tests ()
777 {
778 test_function_point_equality ();
779 test_function_point_ordering ();
780 test_program_point_equality ();
781 }
782
783 } // namespace selftest
784
785 #endif /* CHECKING_P */
786
787 } // namespace ana
788
789 #endif /* #if ENABLE_ANALYZER */
790