blob: 909e3889e28aa2c4cf31fdc28876d54b6d2a461a [file] [log] [blame]
/* Subclasses of diagnostics::paths::event for analyzer diagnostics.
Copyright (C) 2019-2025 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/>. */
#ifndef GCC_ANALYZER_CHECKER_EVENT_H
#define GCC_ANALYZER_CHECKER_EVENT_H
#include "tree-logical-location.h"
#include "analyzer/program-state.h"
#include "analyzer/event-loc-info.h"
#include "diagnostics/digraphs.h"
namespace ana {
/* An enum for discriminating between the concrete subclasses of
checker_event. */
enum class event_kind
{
debug,
custom,
stmt,
region_creation,
function_entry,
state_change,
start_cfg_edge,
end_cfg_edge,
catch_,
call_edge,
return_edge,
start_consolidated_cfg_edges,
end_consolidated_cfg_edges,
inlined_call,
setjmp_,
rewind_from_longjmp,
rewind_to_setjmp,
throw_,
unwind,
warning
};
extern const char *event_kind_to_string (enum event_kind ek);
/* Event subclasses.
The class hierarchy looks like this (using indentation to show
inheritance, and with event_kinds shown for the concrete subclasses):
diagnostics::paths::event
checker_event
debug_event (event_kind::debug)
custom_event (event_kind::custom)
precanned_custom_event
statement_event (event_kind::stmt)
region_creation_event (event_kind::region_creation)
function_entry_event (event_kind::function_entry)
state_change_event (event_kind::state_change)
superedge_event
cfg_edge_event
start_cfg_edge_event (event_kind::start_cfg_edge)
end_cfg_edge_event (event_kind::end_cfg_edge)
catch_cfg_edge_event (event_kind::catch_cfg_edge)
call_event (event_kind::call_edge)
return_edge (event_kind::return_edge)
start_consolidated_cfg_edges_event (event_kind::start_consolidated_cfg_edges)
end_consolidated_cfg_edges_event (event_kind::end_consolidated_cfg_edges)
inlined_call_event (event_kind::inlined_call)
setjmp_event (event_kind::setjmp_)
rewind_event
rewind_from_longjmp_event (event_kind::rewind_from_longjmp)
rewind_to_setjmp_event (event_kind::rewind_to_setjmp)
throw_event (event_kind:throw_)
explicit_throw_event
throw_from_call_to_external_fn_event
unwind_event (event_kind::unwind)
warning_event (event_kind::warning). */
/* Abstract subclass of diagnostics::paths::event; the base class for use in
checker_path (the analyzer's diagnostics::paths::path subclass). */
class checker_event : public diagnostics::paths::event
{
public:
/* Implementation of diagnostics::paths::event. */
location_t get_location () const final override { return m_loc; }
int get_stack_depth () const final override { return m_effective_depth; }
diagnostics::logical_locations::key
get_logical_location () const final override
{
return m_logical_loc;
}
meaning get_meaning () const override;
bool connect_to_next_event_p () const override { return false; }
diagnostics::paths::thread_id_t get_thread_id () const final override
{
return 0;
}
void
maybe_add_sarif_properties (diagnostics::sarif_builder &,
diagnostics::sarif_object &thread_flow_loc_obj)
const override;
/* Additional functionality. */
enum event_kind get_kind () const { return m_kind; }
tree get_fndecl () const { return m_effective_fndecl; }
int get_original_stack_depth () const { return m_original_depth; }
virtual void prepare_for_emission (checker_path *,
pending_diagnostic *pd,
diagnostics::paths::event_id_t emission_id);
virtual bool is_call_p () const { return false; }
virtual bool is_function_entry_p () const { return false; }
virtual bool is_return_p () const { return false; }
std::unique_ptr<diagnostics::digraphs::digraph>
maybe_make_diagnostic_state_graph (bool debug) const final override;
virtual const program_state *
get_program_state () const { return nullptr; }
/* For use with %@. */
const diagnostics::paths::event_id_t *get_id_ptr () const
{
return &m_emission_id;
}
void dump (pretty_printer *pp) const;
void debug () const;
void set_location (location_t loc) { m_loc = loc; }
protected:
checker_event (enum event_kind kind,
const event_loc_info &loc_info);
private:
const checker_path *m_path;
const enum event_kind m_kind;
protected:
location_t m_loc;
tree m_original_fndecl;
tree m_effective_fndecl;
int m_original_depth;
int m_effective_depth;
pending_diagnostic *m_pending_diagnostic;
diagnostics::paths::event_id_t m_emission_id; // only set once all pruning has occurred
diagnostics::logical_locations::key m_logical_loc;
};
/* A concrete event subclass for a purely textual event, for use in
debugging path creation and filtering. */
class debug_event : public checker_event
{
public:
debug_event (const event_loc_info &loc_info,
const char *desc)
: checker_event (event_kind::debug, loc_info),
m_desc (xstrdup (desc))
{
}
~debug_event ()
{
free (m_desc);
}
void print_desc (pretty_printer &) const final override;
private:
char *m_desc;
};
/* An abstract event subclass for custom events. These are not filtered,
as they are likely to be pertinent to the diagnostic. */
class custom_event : public checker_event
{
protected:
custom_event (const event_loc_info &loc_info)
: checker_event (event_kind::custom, loc_info)
{
}
};
/* A concrete custom_event subclass with a precanned message. */
class precanned_custom_event : public custom_event
{
public:
precanned_custom_event (const event_loc_info &loc_info,
const char *desc)
: custom_event (loc_info),
m_desc (xstrdup (desc))
{
}
~precanned_custom_event ()
{
free (m_desc);
}
void print_desc (pretty_printer &) const final override;
private:
char *m_desc;
};
/* A concrete event subclass describing the execution of a gimple statement,
for use at high verbosity levels when debugging paths. */
class statement_event : public checker_event
{
public:
statement_event (const gimple *stmt, tree fndecl, int depth,
const program_state &dst_state);
void print_desc (pretty_printer &) const final override;
const program_state *
get_program_state () const final override
{
return &m_dst_state;
}
const gimple * const m_stmt;
const program_state m_dst_state;
};
/* An abstract event subclass describing the creation of a region that
is significant for a diagnostic.
There are too many combinations to express region creation in one message,
so we emit multiple region_creation_event instances when each pertinent
region is created.
The events are created by pending_diagnostic's add_region_creation_events
vfunc, which by default creates a region_creation_event_memory_space, and
if a capacity is known, a region_creation_event_capacity, giving e.g.:
(1) region created on stack here
(2) capacity: 100 bytes
but this vfunc can be overridden to create other events if other wordings
are more appropriate foa a given pending_diagnostic. */
class region_creation_event : public checker_event
{
protected:
region_creation_event (const event_loc_info &loc_info);
};
/* Concrete subclass of region_creation_event.
Generates a message based on the memory space of the region
e.g. "region created on stack here". */
class region_creation_event_memory_space : public region_creation_event
{
public:
region_creation_event_memory_space (enum memory_space mem_space,
const event_loc_info &loc_info)
: region_creation_event (loc_info),
m_mem_space (mem_space)
{
}
void print_desc (pretty_printer &pp) const final override;
private:
enum memory_space m_mem_space;
};
/* Concrete subclass of region_creation_event.
Generates a message based on the capacity of the region
e.g. "capacity: 100 bytes". */
class region_creation_event_capacity : public region_creation_event
{
public:
region_creation_event_capacity (tree capacity,
const event_loc_info &loc_info)
: region_creation_event (loc_info),
m_capacity (capacity)
{
gcc_assert (m_capacity);
}
void print_desc (pretty_printer &pp) const final override;
private:
tree m_capacity;
};
/* Concrete subclass of region_creation_event.
Generates a message based on the capacity of the region
e.g. "allocated 100 bytes here". */
class region_creation_event_allocation_size : public region_creation_event
{
public:
region_creation_event_allocation_size (tree capacity,
const event_loc_info &loc_info)
: region_creation_event (loc_info),
m_capacity (capacity)
{}
void print_desc (pretty_printer &pp) const final override;
private:
tree m_capacity;
};
/* Concrete subclass of region_creation_event.
Generates a debug message intended for analyzer developers. */
class region_creation_event_debug : public region_creation_event
{
public:
region_creation_event_debug (const region *reg, tree capacity,
const event_loc_info &loc_info)
: region_creation_event (loc_info),
m_reg (reg), m_capacity (capacity)
{
}
void print_desc (pretty_printer &pp) const final override;
private:
const region *m_reg;
tree m_capacity;
};
/* An event subclass describing the entry to a function. */
class function_entry_event : public checker_event
{
public:
function_entry_event (const event_loc_info &loc_info,
const program_state &state)
: checker_event (event_kind::function_entry, loc_info),
m_state (state)
{
}
function_entry_event (const program_point &dst_point,
const program_state &state);
void print_desc (pretty_printer &pp) const override;
meaning get_meaning () const override;
bool is_function_entry_p () const final override { return true; }
const program_state *
get_program_state () const final override
{
return &m_state;
}
private:
const program_state &m_state;
};
/* Subclass of checker_event describing a state change. */
class state_change_event : public checker_event
{
public:
state_change_event (const supernode *node, const gimple *stmt,
int stack_depth,
const state_machine &sm,
const svalue *sval,
state_machine::state_t from,
state_machine::state_t to,
const svalue *origin,
const program_state &dst_state,
const exploded_node *enode);
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
const program_state *
get_program_state () const final override
{
return &m_dst_state;
}
const function *get_dest_function () const
{
return m_dst_state.get_current_function ();
}
const exploded_node *get_exploded_node () const { return m_enode; }
const supernode *m_node;
const gimple *m_stmt;
const state_machine &m_sm;
const svalue *m_sval;
state_machine::state_t m_from;
state_machine::state_t m_to;
const svalue *m_origin;
program_state m_dst_state;
const exploded_node *m_enode;
};
/* Subclass of checker_event; parent class for subclasses that relate to
a superedge. */
class superedge_event : public checker_event
{
public:
void
maybe_add_sarif_properties (diagnostics::sarif_builder &,
diagnostics::sarif_object &thread_flow_loc_obj)
const override;
/* Mark this edge event as being either an interprocedural call or
return in which VAR is in STATE, and that this is critical to the
diagnostic (so that print_desc can attempt to get a better description
from any pending_diagnostic). */
void record_critical_state (tree var, state_machine::state_t state)
{
m_var = var;
m_critical_state = state;
}
const callgraph_superedge& get_callgraph_superedge () const;
bool should_filter_p (int verbosity) const;
const program_state *
get_program_state () const override;
protected:
superedge_event (enum event_kind kind, const exploded_edge &eedge,
const event_loc_info &loc_info);
public:
const exploded_edge &m_eedge;
const superedge *m_sedge;
tree m_var;
state_machine::state_t m_critical_state;
};
/* An abstract event subclass for when a CFG edge is followed; it has two
subclasses, representing the start of the edge and the end of the
edge, which come in pairs. */
class cfg_edge_event : public superedge_event
{
public:
meaning get_meaning () const override;
const cfg_superedge& get_cfg_superedge () const;
protected:
cfg_edge_event (enum event_kind kind, const exploded_edge &eedge,
const event_loc_info &loc_info);
};
/* A concrete event subclass for the start of a CFG edge
e.g. "following 'false' branch...'. */
class start_cfg_edge_event : public cfg_edge_event
{
public:
start_cfg_edge_event (const exploded_edge &eedge,
const event_loc_info &loc_info)
: cfg_edge_event (event_kind::start_cfg_edge, eedge, loc_info)
{
}
void print_desc (pretty_printer &pp) const override;
bool connect_to_next_event_p () const final override { return true; }
protected:
label_text maybe_describe_condition (bool can_colorize) const;
private:
static label_text maybe_describe_condition (bool can_colorize,
tree lhs,
enum tree_code op,
tree rhs);
static bool should_print_expr_p (tree);
};
/* A concrete event subclass for the end of a CFG edge
e.g. "...to here'. */
class end_cfg_edge_event : public cfg_edge_event
{
public:
end_cfg_edge_event (const exploded_edge &eedge,
const event_loc_info &loc_info)
: cfg_edge_event (event_kind::end_cfg_edge, eedge, loc_info)
{
}
void print_desc (pretty_printer &pp) const final override
{
pp_string (&pp, "...to here");
}
};
/* A concrete event subclass for catching an exception
e.g. "...catching 'struct io_error' here". */
class catch_cfg_edge_event : public cfg_edge_event
{
public:
catch_cfg_edge_event (const exploded_edge &eedge,
const event_loc_info &loc_info,
tree type)
: cfg_edge_event (event_kind::catch_, eedge, loc_info),
m_type (type)
{
}
void print_desc (pretty_printer &pp) const final override
{
if (m_type)
pp_printf (&pp, "...catching exception of type %qT here", m_type);
else
pp_string (&pp, "...catching exception here");
}
private:
tree m_type;
};
/* A concrete event subclass for an interprocedural call. */
class call_event : public superedge_event
{
public:
call_event (const exploded_edge &eedge,
const event_loc_info &loc_info);
void print_desc (pretty_printer &pp) const override;
meaning get_meaning () const override;
bool is_call_p () const final override;
const program_state *
get_program_state () const final override;
protected:
tree get_caller_fndecl () const;
tree get_callee_fndecl () const;
const supernode *m_src_snode;
const supernode *m_dest_snode;
};
/* A concrete event subclass for an interprocedural return. */
class return_event : public superedge_event
{
public:
return_event (const exploded_edge &eedge,
const event_loc_info &loc_info);
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
bool is_return_p () const final override;
const supernode *m_src_snode;
const supernode *m_dest_snode;
};
/* A concrete event subclass for the start of a consolidated run of CFG
edges all either TRUE or FALSE e.g. "following 'false' branch...'. */
class start_consolidated_cfg_edges_event : public checker_event
{
public:
start_consolidated_cfg_edges_event (const event_loc_info &loc_info,
bool edge_sense)
: checker_event (event_kind::start_consolidated_cfg_edges, loc_info),
m_edge_sense (edge_sense)
{
}
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
bool connect_to_next_event_p () const final override { return true; }
private:
bool m_edge_sense;
};
/* A concrete event subclass for the end of a consolidated run of
CFG edges e.g. "...to here'. */
class end_consolidated_cfg_edges_event : public checker_event
{
public:
end_consolidated_cfg_edges_event (const event_loc_info &loc_info)
: checker_event (event_kind::end_consolidated_cfg_edges, loc_info)
{
}
void print_desc (pretty_printer &pp) const final override
{
pp_string (&pp, "...to here");
}
};
/* A concrete event subclass for describing an inlined call event
e.g. "inlined call to 'callee' from 'caller'". */
class inlined_call_event : public checker_event
{
public:
inlined_call_event (location_t loc,
tree apparent_callee_fndecl,
tree apparent_caller_fndecl,
int actual_depth,
int stack_depth_adjustment)
: checker_event (event_kind::inlined_call,
event_loc_info (loc,
apparent_caller_fndecl,
actual_depth + stack_depth_adjustment)),
m_apparent_callee_fndecl (apparent_callee_fndecl),
m_apparent_caller_fndecl (apparent_caller_fndecl)
{
gcc_assert (LOCATION_BLOCK (loc) == NULL);
}
void print_desc (pretty_printer &) const final override;
meaning get_meaning () const override;
private:
tree m_apparent_callee_fndecl;
tree m_apparent_caller_fndecl;
};
/* A concrete event subclass for a setjmp or sigsetjmp call. */
class setjmp_event : public checker_event
{
public:
setjmp_event (const event_loc_info &loc_info,
const exploded_node *enode,
const gcall &setjmp_call)
: checker_event (event_kind::setjmp_, loc_info),
m_enode (enode), m_setjmp_call (setjmp_call)
{
}
void print_desc (pretty_printer &pp) const final override;
void prepare_for_emission (checker_path *path,
pending_diagnostic *pd,
diagnostics::paths::event_id_t emission_id) final override;
private:
const exploded_node *m_enode;
const gcall &m_setjmp_call;
};
/* An abstract event subclass for rewinding from a longjmp to a setjmp
(or siglongjmp to sigsetjmp).
Base class for two from/to subclasses, showing the two halves of the
rewind. */
class rewind_event : public checker_event
{
public:
tree get_longjmp_caller () const;
tree get_setjmp_caller () const;
const exploded_edge *get_eedge () const { return m_eedge; }
protected:
rewind_event (const exploded_edge *eedge,
enum event_kind kind,
const event_loc_info &loc_info,
const rewind_info_t *rewind_info);
const rewind_info_t *m_rewind_info;
private:
const exploded_edge *m_eedge;
};
/* A concrete event subclass for rewinding from a longjmp to a setjmp,
showing the longjmp (or siglongjmp). */
class rewind_from_longjmp_event : public rewind_event
{
public:
rewind_from_longjmp_event (const exploded_edge *eedge,
const event_loc_info &loc_info,
const rewind_info_t *rewind_info)
: rewind_event (eedge, event_kind::rewind_from_longjmp, loc_info,
rewind_info)
{
}
void print_desc (pretty_printer &pp) const final override;
};
/* A concrete event subclass for rewinding from a longjmp to a setjmp,
showing the setjmp (or sigsetjmp). */
class rewind_to_setjmp_event : public rewind_event
{
public:
rewind_to_setjmp_event (const exploded_edge *eedge,
const event_loc_info &loc_info,
const rewind_info_t *rewind_info)
: rewind_event (eedge, event_kind::rewind_to_setjmp, loc_info,
rewind_info)
{
}
void print_desc (pretty_printer &pp) const final override;
void prepare_for_emission (checker_path *path,
pending_diagnostic *pd,
diagnostics::paths::event_id_t emission_id) final override;
private:
diagnostics::paths::event_id_t m_original_setjmp_event_id;
};
/* An abstract subclass for throwing/rethrowing an exception. */
class throw_event : public checker_event
{
public:
throw_event (const event_loc_info &loc_info,
const exploded_node *enode,
const gcall &throw_call)
: checker_event (event_kind::throw_, loc_info),
m_enode (enode),
m_throw_call (throw_call)
{
}
protected:
const exploded_node *m_enode;
const gcall &m_throw_call;
};
/* A concrete event subclass for an explicit "throw EXC;"
or "throw;" (actually, a call to __cxa_throw or __cxa_rethrow). */
class explicit_throw_event : public throw_event
{
public:
explicit_throw_event (const event_loc_info &loc_info,
const exploded_node *enode,
const gcall &throw_call,
tree type,
bool is_rethrow)
: throw_event (loc_info, enode, throw_call),
m_type (type),
m_is_rethrow (is_rethrow)
{
}
void print_desc (pretty_printer &pp) const final override;
private:
tree m_type;
bool m_is_rethrow;
};
/* A concrete event subclass for an exception being thrown
from within a call to a function we don't have the body of,
or where we don't know what function was called. */
class throw_from_call_to_external_fn_event : public throw_event
{
public:
throw_from_call_to_external_fn_event (const event_loc_info &loc_info,
const exploded_node *enode,
const gcall &throw_call,
tree fndecl)
: throw_event (loc_info, enode, throw_call),
m_fndecl (fndecl)
{
}
void print_desc (pretty_printer &pp) const final override;
private:
tree m_fndecl;
};
/* A concrete event subclass for unwinding a stack frame when
processing an exception. */
class unwind_event : public checker_event
{
public:
unwind_event (const event_loc_info &loc_info)
: checker_event (event_kind::unwind, loc_info),
m_num_frames (1)
{
}
void print_desc (pretty_printer &pp) const final override;
int m_num_frames;
};
/* Concrete subclass of checker_event for use at the end of a path:
a repeat of the warning message at the end of the path (perhaps with
references to pertinent events that occurred on the way), at the point
where the problem occurs. */
class warning_event : public checker_event
{
public:
warning_event (const event_loc_info &loc_info,
const exploded_node *enode,
const state_machine *sm,
tree var, state_machine::state_t state,
const program_state *program_state_ = nullptr)
: checker_event (event_kind::warning, loc_info),
m_enode (enode),
m_sm (sm), m_var (var), m_state (state)
{
if (program_state_)
m_program_state = std::make_unique<program_state> (*program_state_);
}
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
const program_state *
get_program_state () const final override;
const exploded_node *get_exploded_node () const { return m_enode; }
private:
const exploded_node *m_enode;
const state_machine *m_sm;
tree m_var;
state_machine::state_t m_state;
/* Optional copy of program state, for when this is different from
m_enode's state: */
std::unique_ptr<program_state> m_program_state;
};
} // namespace ana
#endif /* GCC_ANALYZER_CHECKER_EVENT_H */