719 lines
18 KiB
C++
719 lines
18 KiB
C++
/* Classes for representing locations within the program.
|
|
Copyright (C) 2019-2021 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "tree.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "gcc-rich-location.h"
|
|
#include "json.h"
|
|
#include "analyzer/call-string.h"
|
|
#include "ordered-hash-map.h"
|
|
#include "options.h"
|
|
#include "cgraph.h"
|
|
#include "function.h"
|
|
#include "cfg.h"
|
|
#include "basic-block.h"
|
|
#include "gimple.h"
|
|
#include "gimple-iterator.h"
|
|
#include "digraph.h"
|
|
#include "analyzer/analyzer.h"
|
|
#include "analyzer/analyzer-logging.h"
|
|
#include "analyzer/supergraph.h"
|
|
#include "analyzer/program-point.h"
|
|
#include "sbitmap.h"
|
|
#include "bitmap.h"
|
|
#include "tristate.h"
|
|
#include "selftest.h"
|
|
#include "analyzer/store.h"
|
|
#include "analyzer/region-model.h"
|
|
#include "analyzer/sm.h"
|
|
#include "analyzer/program-state.h"
|
|
#include "alloc-pool.h"
|
|
#include "fibonacci_heap.h"
|
|
#include "diagnostic-event-id.h"
|
|
#include "analyzer/pending-diagnostic.h"
|
|
#include "analyzer/diagnostic-manager.h"
|
|
#include "shortest-paths.h"
|
|
#include "analyzer/exploded-graph.h"
|
|
#include "analyzer/analysis-plan.h"
|
|
|
|
#if ENABLE_ANALYZER
|
|
|
|
namespace ana {
|
|
|
|
/* Get a string for PK. */
|
|
|
|
const char *
|
|
point_kind_to_string (enum point_kind pk)
|
|
{
|
|
switch (pk)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_ORIGIN:
|
|
return "PK_ORIGIN";
|
|
case PK_BEFORE_SUPERNODE:
|
|
return "PK_BEFORE_SUPERNODE";
|
|
case PK_BEFORE_STMT:
|
|
return "PK_BEFORE_STMT";
|
|
case PK_AFTER_SUPERNODE:
|
|
return "PK_AFTER_SUPERNODE";
|
|
case PK_EMPTY:
|
|
return "PK_EMPTY";
|
|
case PK_DELETED:
|
|
return "PK_DELETED";
|
|
}
|
|
}
|
|
|
|
/* class function_point. */
|
|
|
|
function_point::function_point (const supernode *supernode,
|
|
const superedge *from_edge,
|
|
unsigned stmt_idx,
|
|
enum point_kind kind)
|
|
: m_supernode (supernode), m_from_edge (from_edge),
|
|
m_stmt_idx (stmt_idx), m_kind (kind)
|
|
{
|
|
if (from_edge)
|
|
{
|
|
gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
|
|
gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
|
|
}
|
|
if (stmt_idx)
|
|
gcc_checking_assert (m_kind == PK_BEFORE_STMT);
|
|
}
|
|
|
|
/* Print this function_point to PP. */
|
|
|
|
void
|
|
function_point::print (pretty_printer *pp, const format &f) const
|
|
{
|
|
switch (get_kind ())
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case PK_ORIGIN:
|
|
pp_printf (pp, "origin");
|
|
break;
|
|
|
|
case PK_BEFORE_SUPERNODE:
|
|
{
|
|
if (m_from_edge)
|
|
pp_printf (pp, "before SN: %i (from SN: %i)",
|
|
m_supernode->m_index, m_from_edge->m_src->m_index);
|
|
else
|
|
pp_printf (pp, "before SN: %i (NULL from-edge)",
|
|
m_supernode->m_index);
|
|
f.spacer (pp);
|
|
for (gphi_iterator gpi
|
|
= const_cast<supernode *>(get_supernode ())->start_phis ();
|
|
!gsi_end_p (gpi); gsi_next (&gpi))
|
|
{
|
|
const gphi *phi = gpi.phi ();
|
|
pp_gimple_stmt_1 (pp, phi, 0, (dump_flags_t)0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PK_BEFORE_STMT:
|
|
pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
|
|
m_stmt_idx);
|
|
f.spacer (pp);
|
|
pp_gimple_stmt_1 (pp, get_stmt (), 0, (dump_flags_t)0);
|
|
if (f.m_newlines)
|
|
{
|
|
pp_newline (pp);
|
|
print_source_line (pp);
|
|
}
|
|
break;
|
|
|
|
case PK_AFTER_SUPERNODE:
|
|
pp_printf (pp, "after SN: %i", m_supernode->m_index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Generate a hash value for this function_point. */
|
|
|
|
hashval_t
|
|
function_point::hash () const
|
|
{
|
|
inchash::hash hstate;
|
|
if (m_supernode)
|
|
hstate.add_int (m_supernode->m_index);
|
|
hstate.add_ptr (m_from_edge);
|
|
hstate.add_int (m_stmt_idx);
|
|
hstate.add_int (m_kind);
|
|
return hstate.end ();
|
|
}
|
|
|
|
/* Get the function at this point, if any. */
|
|
|
|
function *
|
|
function_point::get_function () const
|
|
{
|
|
if (m_supernode)
|
|
return m_supernode->m_fun;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the gimple stmt for this function_point, if any. */
|
|
|
|
const gimple *
|
|
function_point::get_stmt () const
|
|
{
|
|
if (m_kind == PK_BEFORE_STMT)
|
|
return m_supernode->m_stmts[m_stmt_idx];
|
|
else if (m_kind == PK_AFTER_SUPERNODE)
|
|
return m_supernode->get_last_stmt ();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* Get a location for this function_point, if any. */
|
|
|
|
location_t
|
|
function_point::get_location () const
|
|
{
|
|
const gimple *stmt = get_stmt ();
|
|
if (stmt)
|
|
return stmt->location;
|
|
if (m_kind == PK_BEFORE_SUPERNODE)
|
|
return m_supernode->get_start_location ();
|
|
else if (m_kind == PK_AFTER_SUPERNODE)
|
|
return m_supernode->get_end_location ();
|
|
else
|
|
return UNKNOWN_LOCATION;
|
|
}
|
|
|
|
/* Create a function_point representing the entrypoint of function FUN. */
|
|
|
|
function_point
|
|
function_point::from_function_entry (const supergraph &sg, function *fun)
|
|
{
|
|
return before_supernode (sg.get_node_for_function_entry (fun), NULL);
|
|
}
|
|
|
|
/* Create a function_point representing entering supernode SUPERNODE,
|
|
having reached it via FROM_EDGE (which could be NULL). */
|
|
|
|
function_point
|
|
function_point::before_supernode (const supernode *supernode,
|
|
const superedge *from_edge)
|
|
{
|
|
if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
|
|
from_edge = NULL;
|
|
return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
|
|
}
|
|
|
|
/* A subclass of diagnostic_context for use by
|
|
program_point::print_source_line. */
|
|
|
|
class debug_diagnostic_context : public diagnostic_context
|
|
{
|
|
public:
|
|
debug_diagnostic_context ()
|
|
{
|
|
diagnostic_initialize (this, 0);
|
|
show_line_numbers_p = true;
|
|
show_caret = true;
|
|
}
|
|
~debug_diagnostic_context ()
|
|
{
|
|
diagnostic_finish (this);
|
|
}
|
|
};
|
|
|
|
/* Print the source line (if any) for this function_point to PP. */
|
|
|
|
void
|
|
function_point::print_source_line (pretty_printer *pp) const
|
|
{
|
|
const gimple *stmt = get_stmt ();
|
|
if (!stmt)
|
|
return;
|
|
// TODO: monospace font
|
|
debug_diagnostic_context tmp_dc;
|
|
gcc_rich_location richloc (stmt->location);
|
|
diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
|
|
pp_string (pp, pp_formatted_text (tmp_dc.printer));
|
|
}
|
|
|
|
/* class program_point. */
|
|
|
|
/* Print this program_point to PP. */
|
|
|
|
void
|
|
program_point::print (pretty_printer *pp, const format &f) const
|
|
{
|
|
pp_string (pp, "callstring: ");
|
|
m_call_string.print (pp);
|
|
f.spacer (pp);
|
|
|
|
m_function_point.print (pp, f);
|
|
}
|
|
|
|
/* Dump this point to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
program_point::dump () const
|
|
{
|
|
pretty_printer pp;
|
|
pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
pp.buffer->stream = stderr;
|
|
print (&pp, format (true));
|
|
pp_flush (&pp);
|
|
}
|
|
|
|
/* Return a new json::object of the form
|
|
{"kind" : str,
|
|
"snode_idx" : int (optional), the index of the supernode,
|
|
"from_edge_snode_idx" : int (only for kind=='PK_BEFORE_SUPERNODE'),
|
|
"stmt_idx": int (only for kind=='PK_BEFORE_STMT',
|
|
"call_string": object for the call_string}. */
|
|
|
|
json::object *
|
|
program_point::to_json () const
|
|
{
|
|
json::object *point_obj = new json::object ();
|
|
|
|
point_obj->set ("kind",
|
|
new json::string (point_kind_to_string (get_kind ())));
|
|
|
|
if (get_supernode ())
|
|
point_obj->set ("snode_idx",
|
|
new json::integer_number (get_supernode ()->m_index));
|
|
|
|
switch (get_kind ())
|
|
{
|
|
default: break;
|
|
case PK_BEFORE_SUPERNODE:
|
|
if (const superedge *sedge = get_from_edge ())
|
|
point_obj->set ("from_edge_snode_idx",
|
|
new json::integer_number (sedge->m_src->m_index));
|
|
break;
|
|
case PK_BEFORE_STMT:
|
|
point_obj->set ("stmt_idx", new json::integer_number (get_stmt_idx ()));
|
|
break;
|
|
}
|
|
|
|
point_obj->set ("call_string", m_call_string.to_json ());
|
|
|
|
return point_obj;
|
|
}
|
|
|
|
/* Generate a hash value for this program_point. */
|
|
|
|
hashval_t
|
|
program_point::hash () const
|
|
{
|
|
inchash::hash hstate;
|
|
hstate.merge_hash (m_function_point.hash ());
|
|
hstate.merge_hash (m_call_string.hash ());
|
|
return hstate.end ();
|
|
}
|
|
|
|
/* Get the function * at DEPTH within the call stack. */
|
|
|
|
function *
|
|
program_point::get_function_at_depth (unsigned depth) const
|
|
{
|
|
gcc_assert (depth <= m_call_string.length ());
|
|
if (depth == m_call_string.length ())
|
|
return m_function_point.get_function ();
|
|
else
|
|
return m_call_string[depth]->get_caller_function ();
|
|
}
|
|
|
|
/* Assert that this object is sane. */
|
|
|
|
void
|
|
program_point::validate () const
|
|
{
|
|
/* Skip this in a release build. */
|
|
#if !CHECKING_P
|
|
return;
|
|
#endif
|
|
|
|
m_call_string.validate ();
|
|
/* The "callee" of the final entry in the callstring should be the
|
|
function of the m_function_point. */
|
|
if (m_call_string.length () > 0)
|
|
gcc_assert (m_call_string[m_call_string.length () - 1]->get_callee_function ()
|
|
== get_function ());
|
|
}
|
|
|
|
/* Check to see if SUCC is a valid edge to take (ensuring that we have
|
|
interprocedurally valid paths in the exploded graph, and enforcing
|
|
recursion limits).
|
|
|
|
Update the call string if SUCC is a call or a return.
|
|
|
|
Return true if SUCC can be taken, or false otherwise.
|
|
|
|
This is the "point" half of exploded_node::on_edge. */
|
|
|
|
bool
|
|
program_point::on_edge (exploded_graph &eg,
|
|
const superedge *succ)
|
|
{
|
|
logger * const logger = eg.get_logger ();
|
|
LOG_FUNC (logger);
|
|
switch (succ->m_kind)
|
|
{
|
|
case SUPEREDGE_CFG_EDGE:
|
|
{
|
|
const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (succ);
|
|
|
|
/* Reject abnormal edges; we special-case setjmp/longjmp. */
|
|
if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case SUPEREDGE_CALL:
|
|
{
|
|
const call_superedge *call_sedge = as_a <const call_superedge *> (succ);
|
|
|
|
if (eg.get_analysis_plan ().use_summary_p (call_sedge->m_cedge))
|
|
{
|
|
if (logger)
|
|
logger->log ("rejecting call edge: using summary instead");
|
|
return false;
|
|
}
|
|
|
|
/* Add the callsite to the call string. */
|
|
m_call_string.push_call (eg.get_supergraph (), call_sedge);
|
|
|
|
/* Impose a maximum recursion depth and don't analyze paths
|
|
that exceed it further.
|
|
This is something of a blunt workaround, but it only
|
|
applies to recursion (and mutual recursion), not to
|
|
general call stacks. */
|
|
if (m_call_string.calc_recursion_depth ()
|
|
> param_analyzer_max_recursion_depth)
|
|
{
|
|
if (logger)
|
|
logger->log ("rejecting call edge: recursion limit exceeded");
|
|
// TODO: issue a sorry for this?
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SUPEREDGE_RETURN:
|
|
{
|
|
/* Require that we return to the call site in the call string. */
|
|
if (m_call_string.empty_p ())
|
|
{
|
|
if (logger)
|
|
logger->log ("rejecting return edge: empty call string");
|
|
return false;
|
|
}
|
|
const return_superedge *top_of_stack = m_call_string.pop ();
|
|
if (top_of_stack != succ)
|
|
{
|
|
if (logger)
|
|
logger->log ("rejecting return edge: return to wrong callsite");
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SUPEREDGE_INTRAPROCEDURAL_CALL:
|
|
{
|
|
const callgraph_superedge *cg_sedge
|
|
= as_a <const callgraph_superedge *> (succ);
|
|
/* Consider turning this edge into a use of an
|
|
interprocedural summary. */
|
|
if (eg.get_analysis_plan ().use_summary_p (cg_sedge->m_cedge))
|
|
{
|
|
if (logger)
|
|
logger->log ("using function summary for %qE in %qE",
|
|
cg_sedge->get_callee_decl (),
|
|
cg_sedge->get_caller_decl ());
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, we ignore these edges */
|
|
if (logger)
|
|
logger->log ("rejecting interprocedural edge");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Comparator for program points within the same supernode,
|
|
for implementing worklist::key_t comparison operators.
|
|
Return negative if POINT_A is before POINT_B
|
|
Return positive if POINT_A is after POINT_B
|
|
Return 0 if they are equal. */
|
|
|
|
int
|
|
function_point::cmp_within_supernode_1 (const function_point &point_a,
|
|
const function_point &point_b)
|
|
{
|
|
gcc_assert (point_a.get_supernode () == point_b.get_supernode ());
|
|
|
|
switch (point_a.m_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_BEFORE_SUPERNODE:
|
|
switch (point_b.m_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_BEFORE_SUPERNODE:
|
|
{
|
|
int a_src_idx = -1;
|
|
int b_src_idx = -1;
|
|
if (point_a.m_from_edge)
|
|
a_src_idx = point_a.m_from_edge->m_src->m_index;
|
|
if (point_b.m_from_edge)
|
|
b_src_idx = point_b.m_from_edge->m_src->m_index;
|
|
return a_src_idx - b_src_idx;
|
|
}
|
|
break;
|
|
|
|
case PK_BEFORE_STMT:
|
|
case PK_AFTER_SUPERNODE:
|
|
return -1;
|
|
}
|
|
break;
|
|
case PK_BEFORE_STMT:
|
|
switch (point_b.m_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_BEFORE_SUPERNODE:
|
|
return 1;
|
|
|
|
case PK_BEFORE_STMT:
|
|
return point_a.m_stmt_idx - point_b.m_stmt_idx;
|
|
|
|
case PK_AFTER_SUPERNODE:
|
|
return -1;
|
|
}
|
|
break;
|
|
case PK_AFTER_SUPERNODE:
|
|
switch (point_b.m_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_BEFORE_SUPERNODE:
|
|
case PK_BEFORE_STMT:
|
|
return 1;
|
|
|
|
case PK_AFTER_SUPERNODE:
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Comparator for program points within the same supernode,
|
|
for implementing worklist::key_t comparison operators.
|
|
Return negative if POINT_A is before POINT_B
|
|
Return positive if POINT_A is after POINT_B
|
|
Return 0 if they are equal. */
|
|
|
|
int
|
|
function_point::cmp_within_supernode (const function_point &point_a,
|
|
const function_point &point_b)
|
|
{
|
|
int result = cmp_within_supernode_1 (point_a, point_b);
|
|
|
|
/* Check that the ordering is symmetric */
|
|
#if CHECKING_P
|
|
int reversed = cmp_within_supernode_1 (point_b, point_a);
|
|
gcc_assert (reversed == -result);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Comparator for imposing an order on function_points. */
|
|
|
|
int
|
|
function_point::cmp (const function_point &point_a,
|
|
const function_point &point_b)
|
|
{
|
|
int idx_a = point_a.m_supernode ? point_a.m_supernode->m_index : -1;
|
|
int idx_b = point_b.m_supernode ? point_b.m_supernode->m_index : -1;
|
|
if (int cmp_idx = idx_a - idx_b)
|
|
return cmp_idx;
|
|
gcc_assert (point_a.m_supernode == point_b.m_supernode);
|
|
if (point_a.m_supernode)
|
|
return cmp_within_supernode (point_a, point_b);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* Comparator for use by vec<function_point>::qsort. */
|
|
|
|
int
|
|
function_point::cmp_ptr (const void *p1, const void *p2)
|
|
{
|
|
const function_point *fp1 = (const function_point *)p1;
|
|
const function_point *fp2 = (const function_point *)p2;
|
|
return cmp (*fp1, *fp2);
|
|
}
|
|
|
|
/* For PK_BEFORE_STMT, go to next stmt (or to PK_AFTER_SUPERNODE). */
|
|
|
|
void
|
|
function_point::next_stmt ()
|
|
{
|
|
gcc_assert (m_kind == PK_BEFORE_STMT);
|
|
if (++m_stmt_idx == m_supernode->m_stmts.length ())
|
|
{
|
|
m_kind = PK_AFTER_SUPERNODE;
|
|
m_stmt_idx = 0;
|
|
}
|
|
}
|
|
|
|
/* For those program points for which there is a uniquely-defined
|
|
successor, return it. */
|
|
|
|
program_point
|
|
program_point::get_next () const
|
|
{
|
|
switch (m_function_point.get_kind ())
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case PK_ORIGIN:
|
|
case PK_AFTER_SUPERNODE:
|
|
gcc_unreachable (); /* Not uniquely defined. */
|
|
case PK_BEFORE_SUPERNODE:
|
|
if (get_supernode ()->m_stmts.length () > 0)
|
|
return before_stmt (get_supernode (), 0, get_call_string ());
|
|
else
|
|
return after_supernode (get_supernode (), get_call_string ());
|
|
case PK_BEFORE_STMT:
|
|
{
|
|
unsigned next_idx = get_stmt_idx ();
|
|
if (next_idx < get_supernode ()->m_stmts.length ())
|
|
return before_stmt (get_supernode (), next_idx, get_call_string ());
|
|
else
|
|
return after_supernode (get_supernode (), get_call_string ());
|
|
}
|
|
}
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
namespace selftest {
|
|
|
|
/* Verify that function_point::operator== works as expected. */
|
|
|
|
static void
|
|
test_function_point_equality ()
|
|
{
|
|
const supernode *snode = NULL;
|
|
|
|
function_point a = function_point (snode, NULL, 0,
|
|
PK_BEFORE_SUPERNODE);
|
|
function_point b = function_point::before_supernode (snode, NULL);
|
|
ASSERT_EQ (a, b);
|
|
}
|
|
|
|
/* Verify that function_point::cmp_within_supernode works as expected. */
|
|
|
|
static void
|
|
test_function_point_ordering ()
|
|
{
|
|
const supernode *snode = NULL;
|
|
const call_string call_string;
|
|
|
|
/* Populate an array with various points within the same
|
|
snode, in order. */
|
|
auto_vec<function_point> points;
|
|
points.safe_push (function_point::before_supernode (snode, NULL));
|
|
points.safe_push (function_point::before_stmt (snode, 0));
|
|
points.safe_push (function_point::before_stmt (snode, 1));
|
|
points.safe_push (function_point::after_supernode (snode));
|
|
|
|
/* Check all pairs. */
|
|
unsigned i;
|
|
function_point *point_a;
|
|
FOR_EACH_VEC_ELT (points, i, point_a)
|
|
{
|
|
unsigned j;
|
|
function_point *point_b;
|
|
FOR_EACH_VEC_ELT (points, j, point_b)
|
|
{
|
|
int cmp = function_point::cmp_within_supernode (*point_a, *point_b);
|
|
if (i == j)
|
|
ASSERT_EQ (cmp, 0);
|
|
if (i < j)
|
|
ASSERT_TRUE (cmp < 0);
|
|
if (i > j)
|
|
ASSERT_TRUE (cmp > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Verify that program_point::operator== works as expected. */
|
|
|
|
static void
|
|
test_program_point_equality ()
|
|
{
|
|
const supernode *snode = NULL;
|
|
|
|
const call_string cs;
|
|
|
|
program_point a = program_point::before_supernode (snode, NULL,
|
|
cs);
|
|
|
|
program_point b = program_point::before_supernode (snode, NULL,
|
|
cs);
|
|
|
|
ASSERT_EQ (a, b);
|
|
// TODO: verify with non-empty callstrings, with different edges
|
|
}
|
|
|
|
/* Run all of the selftests within this file. */
|
|
|
|
void
|
|
analyzer_program_point_cc_tests ()
|
|
{
|
|
test_function_point_equality ();
|
|
test_function_point_ordering ();
|
|
test_program_point_equality ();
|
|
}
|
|
|
|
} // namespace selftest
|
|
|
|
#endif /* CHECKING_P */
|
|
|
|
} // namespace ana
|
|
|
|
#endif /* #if ENABLE_ANALYZER */
|