GCC's static analyzer code has become hard to debug and extend.
I've realized that the core data structures within it for tracking
positions in the user's code are clunky and make things more difficult
than they need to be.
The analyzer has a data structure called the "supergraph" which unifies
all CFGs and the callgraph into a single directed graph expressing
control flow and function calls in the user's code. The core job of the
analyzer is to walk paths in the supergraph to build a directed graph
called the exploded graph, which combines control flow and data flow,
and uncovers problems as it does so (e.g. double-free bugs).
Previously, the nodes in the supergraph closely matched basic blocks in
the gimple CFG representation in the hope that this would help the
analyzer scale better, using a class function_point to refer to places
in the code, such as *within* a basic block/supernode. This approach
needed lots of awkward special cases and workarounds to deal with state
changes that happen mid-node, which complicated the implementation and
make debugging it hard.
This patch reimplements the analyzer's supergraph:
* eliminate class function_point in favor of a very fine-grained
supergraph, where each node in the graph represents a location in the
user's program, and each edge in the graph represents an operation (with
no-op edges for showing changing locations). The debug option
"-fanalyzer-fine-grained" becomes redundant.
* eliminate the class hierarchy inheriting from class superedge in
favor of having each superedge optionally own an "operation", to better
express the state transitions along edges (composition rather than
inheritance), and splitting up the more complicated cases into
multiple operations/edges (making debugging easier and reasoning about
state transitions clearer).
* perform various post-processing "passes" to the supergraph after it's
initially constructed but before performing the analysis, such as
simplifying the graph, improving source location information, etc
* eliminate class stmt_finder (which was always something of a hack) in
favor of improving user source locations in the supergraph, using
class event_loc_info more consistently, and a new class
pending_location::fixup_for_epath for the most awkward cases (leaks)
* precompute and cache various properties in operations, such as for
switch edges and for phi edges, rather than performing work each time we
visit an edge.
Advantages:
* The implementation is much simpler, easier to understand and debug,
and has much clearer separation of responsibilities.
* Locations for diagnostics are somewhat improved (due to being more
consistent about using the goto_locus field of CFG edges when
constructing the supergraph, and fixing up missing location data from
gimple stmts).
* The analyzer now detects a missing "return" from a non-void-returning
function (albeit as a read of uninitialized "<return-value>"), which
found many lurking true +ves in the test suite. I can fix the wording
of this case as a follow-up.
Disadvantages:
* The supergraph is much larger than before (one node per gimple stmt,
rather than per basic block) - but the optimizer that runs after the
supergraph is built simplifies it somewhat (and I have various ideas for
future simplifications which I hope will help the analyzer scale).
* all edges in the supergraph are intraprocedural, making "supergraph"
a misnomer.
Other notes:
* I tried to maintain the behavior of -fanalyzer as closely as possible,
but there are changes to the testsuite output. These mostly are places
where the exploration of the exploded graph leads to nodes not being
merged as well as the previous implementation on a particular test case,
leading to the analysis hitting a termination limit and bailing out.
So I expect the analyzer's behavior to change somewhat. I had to add
xfails in various places - but was able to remove xfails in others.
* the testsuite was running with -fanalyzer-call-summaries enabled,
which is not the default for users. The new implementation uncovered
numerous pre-existing bugs in -fanalyzer-call-summaries, so the patch
disables this within the testsuite, matching the default for users.
Fixing those bugs can be done separately from the patch.
* the only performance data I have so far is with a debug rather than
release build. "wall" time spent in the analyzer shows a slight
improvement overall, but with one new outlier in the integration
testsuite that now takes over an hour (specifically,
qemu-7.2.0/build/target_hexagon_decode.c) but I'd like to go ahead with
pushing this, and treat that specific slowdown as a bug.
I posted an incomplete version of this before the close of stage 1 here:
https://gcc.gnu.org/pipermail/gcc-patches/2025-November/700883.html
Although the patch is a very large change to -fanalyzer, the changes are
confined to that component (apart from a trivial addition of
INCLUDE_DEQUE/#include <deque> to system.h), so I want to apply this
patch now in stage 3: it's a big quality-of-life improvement when
debugging -fanalyzer.
gcc/ChangeLog:
PR analyzer/122003
* Makefile.in (ANALYZER_OBJS): Add analyzer/ops.o,
analyzer/supergraph-fixup-locations.o,
analyzer/supergraph-simplify.o, and analyzer/supergraph-sorting.o.
* digraph.h (dnode::add_in_edge): New.
(dnode::remove_in_edge): New.
(dnode::add_out_edge): New.
(dnode::remove_out_edge): New.
(dnode::m_preds): Make public.
(dnode::m_succs): Likewise.
(dnode::find_edge_idx): New.
(dedge::edge_t): New typedef.
(dedge::m_src): Make non-const.
(dedge::m_dest): Likewise.
(dedge::set_dest): New.
(digraph::add_any_extra_stmts): New.
(digraph<GraphTraits>::dump_dot_to_pp): Call it.
* doc/analyzer.texi: Update for rewrite of supergraph.
* doc/invoke.texi (fanalyzer-fine-grained): Make this as a no-op
preserved for backwards compatibility.
(fanalyzer-simplify-supergraph): Document new option.
(fdump-analyzer-supergraph): Update for changes to output.
* gdbhooks.py (AnaSupernodePrinter.to_string): Update for renaming
of supernode::m_index to supernode::m_id.
* system.h: Include <deque> if INCLUDE_DEQUE was defined.
gcc/analyzer/ChangeLog:
PR analyzer/122003
* analyzer-logging.h (class log_nesting_level): New.
(log_nesting_level::log_nesting_level): New.
(log_nesting_level::~log_nesting_level): New.
* analyzer.cc (is_cxa_end_catch_p): New.
* analyzer.opt (fdump-analyzer-callgraph): Make this as a no-op
preserved for backwards compatibility.
(fanalyzer-fine-grained): Likewise.
(fanalyzer-simplify-supergraph): New.
* bounds-checking.cc (strip_types): Update for changes to
widening_svalue.
* call-details.h: Include "pending-diagnostic.h".
* call-info.cc (custom_edge_info::get_dot_attrs): New.
(call_info::add_events_to_path): Add pending_diagnostic & param.
Fix indentation.
* call-info.h (call_info::add_events_to_path): Add
pending_diagnostic & param.
* call-string.cc (call_string::element_t::operator==): Reimplement.
(call_string::element_t::cmp): New.
(call_string::element_t::get_caller_function): Likewise.
(call_string::element_t::get_callee_function): Likewise.
(call_string::element_t::get_call_snode_in_caller): New.
(call_string::element_t::get_return_snode_in_caller): New.
(call_string::element_t::get_call_stmt): New.
(call_string::print): Update for new implementation.
(call_string::to_json): Likewise.
(call_string::push_call): Likewise.
(call_string::count_occurrences_of_function): Likewise.
(call_string::cmp): Likewise.
(call_string::get_callee_node): Delete.
(call_string::get_caller_node): Convert into...
(call_string::get_return_node_in_caller): ...this new function.
(call_string::validate): Update for new implementation.
(call_string::recursive_log): Likewise.
* call-string.h (class call_superedge): Delete forward decl.
(class return_superedge): Likewise.
(class call_and_return_op): New forward decl.
(struct call_string::element_t): Reimplement using
call_and_return_op, rather than relying on interprocedural edges
in the supergraph.
(class call_string): Likewise.
* call-summary.cc (call_summary::get_fndecl): Reimplement.
(call_summary_replay::convert_svalue_from_summary_1): Update for
changes to widening_svalue.
* checker-event.cc (event_kind_to_string): Update for renamings
of event_kind::{call_edge -> call_} and
event_kind::{return_edge -> return_}.
(region_creation_event_debug::print_desc): Update for change to
event_loc_info.
(state_change_event::state_change_event): Pass in event_loc_info
rather than stack_depth, and pass it to checker_event ctor.
(superedge_event::get_callgraph_superedge): Delete.
(superedge_event::should_filter_p): Reimplement in terms of
get_any_cfg_edge.
(superedge_event::get_call_and_return_op): New.
(superedge_event::superedge_event): Drop m_eedge and
m_critical_state. Add assertion that the superedge is non-null.
(cfg_edge_event::get_cfg_superedge): Delete.
(cfg_edge_event::cfg_edge_event): Add "op" param, and remove
assertion refering to kinds of superedge.
(cfg_edge_event::get_meaning): Reimplement without
cfg_superedge.
(cfg_edge_event::get_cfg_edge): New.
(start_cfg_edge_event::print_desc): Use m_op. Update for renaming
of superedge::m_index to superedge::m_id.
(start_cfg_edge_event::maybe_describe_condition): Reimplement in
ops.cc as gcond_edge_op::maybe_describe_condition.
(start_cfg_edge_event::should_print_expr_p): Reimplement in ops.cc
as gcond_edge_op::should_print_expr_p.
(call_event::call_event): Update for renaming of event_kind. Drop
assertion about superedge kind.
(call_event::print_desc): Update for consolidation of m_var and
m_critical_state into a struct.
(return_event::return_event): Inherit directly from checker_event.
Drop assertion referring to kinds of superedge. Initialize m_edge
and m_call_and_return_op.
(return_event::print_desc): Update for change to m_critical_state.
* checker-event.h (enum class event_kind): Rename call_edge to
call_, and return_edge to return_.
(state_change_event::state_change_event): Update for changes to
location-handling in base class ctor.
(state_change_event::record_critical_state): Drop this, moving
it to special-cases in the subclasses that need it.
(state_change_event::get_callgraph_superedge): Delete.
(superedge_event::get_call_and_return_op): New vfunc decl.
(superedge_event::m_var, superedge_event::m_critical_state): Drop
these fields from this class, combining them into a new struct
and moving the fields to the interprocedural event subclasses
where they make sense.
(cfg_edge_event::get_cfg_superedge): Delete.
(cfg_edge_event::get_cfg_edge): Add.
(cfg_edge_event::cfg_edge_event): Update for changes to location
handling in base class ctor. Add "op".
(cfg_edge_event::m_op): New field.
(start_cfg_edge_event::start_cfg_edge_event): Update for changes
to base class ctor.
(start_cfg_edge_event::maybe_describe_condition): Drop.
(end_cfg_edge_event::end_cfg_edge_event): Update for changes to
base class ctor.
(catch_cfg_edge_event::catch_cfg_edge_event): Likewise.
(struct critical_state): New struct.
(call_event::record_critical_state): New decl.
(call_event::m_critical_state): New field.
(class return_event): Inherit from checker_event, rather than
superedge_event.
(return_event::get_call_and_return_op): New.
(return_event::record_critical_state): New.
(return_event::m_call_and_return_op): New field.
(return_event::m_critical_state): New field.
* common.h: Define INCLUDE_SET.
(class cfg_superedge): Drop forward decl.
(class switch_cfg_superedge): Likewise.
(class eh_dispatch_cfg_superedge): Likewise.
(class eh_dispatch_try_cfg_superedge): Likewise.
(class eh_dispatch_allowed_cfg_superedge): Likewise.
(class callgraph_superedge): Likewise.
(class call_superedge): Likewise.
(class return_superedge): Likewise.
(class stmt_finder): Likewise.
(class function_point): Likewise.
(class feasibility_state): New forward decl.
(class uncertainty_t): Likewise.
(useful_location_p): New.
(known_function::check_any_preconditions): New.
(custom_edge_info::get_dot_attrs): New decl.
(custom_edge_info::add_events_to_path): Add param
"pending_diagnostic &pd".
(is_cxa_end_catch_p): New decl.
* constraint-manager.cc
(bounded_ranges_manager::get_or_create_ranges_for_switch): Delete.
(bounded_ranges_manager::create_ranges_for_switch): Delete.
* constraint-manager.h
(bounded_ranges_manager::get_or_create_ranges_for_switch): Delete
decl.
(bounded_ranges_manager::create_ranges_for_switch): Likewise.
(bounded_ranges_manager::make_case_label_ranges): Make public for
use by ops code.
(bounded_ranges_manager::edge_cache_t): Delete.
(bounded_ranges_manager::m_edge_cache): Delete.
* diagnostic-manager.cc (pending_location::pending_location): New
ctor implementations.
(pending_location::to_json): New.
(epath_finder::get_best_epath): Rename param to "target_enode".
Drop param "target_stmt". Update for renaming of
supernode::m_index to m_id.
(epath_finder::explore_feasible_paths): Drop param "target_stmt".
(process_worklist_item): Likewise.
(saved_diagnostic::saved_diagnostic): Pass in param "ploc" by
rvalue reference and store it in m_ploc. Drop m_stmt_finder and
other fields made redundant by m_ploc.
(saved_diagnostic::get_supernode): New.
(saved_diagnostic::operator==): Update for changes to
location-tracking.
(saved_diagnostic::to_json): Update.
(saved_diagnostic::dump_as_dot_node): Drop m_stmt.
(saved_diagnostic::calc_best_epath): Update for change to
location-tracking.
(saved_diagnostic::supercedes_p): Likewise.
(saved_diagnostic::maybe_add_sarif_properties): Likewise.
(get_emission_location): Delete.
(diagnostic_manager::add_diagnostic): Pass "ploc" by rvalue
reference, moving it to the saved_diagnostic. Update early
rejection check, and call fixup_location before-hand.
(class dedupe_key): Drop m_stmt field, and update for changes
to saved_diagnostic.
(dedupe_winners::add): Call get_best_epath here, and call
fixer_for_epath on it.
(diagnostic_manager::emit_saved_diagnostics): Update for
changes to saved_diagnostic and supernode.
(diagnostic_manager::emit_saved_diagnostic): Likewise.
Use the pending_location from the saved_diagnostic for the
location of the final event, and for the primary location of the
diagnostic itself.
(diagnostic_manager::build_emission_path): Use useful_location_p.
(state_change_event_creator::on_global_state_change): Update for
changes to location-tracking.
(state_change_event_creator::on_state_change): Likewise.
(struct null_assignment_sm_context): Reimplement within ops.cc.
(diagnostic_manager::add_events_for_eedge): Reimplement.
(diagnostic_manager::add_events_for_superedge): Delete in favor of
control_flow_op::add_any_events_for_eedge.
(diagnostic_manager::prune_for_sm_diagnostic): Update call/return
for event_kind renamings, and to use call_and_return_op rathern
than callgraph_superedge.
(diagnostic_manager::consolidate_conditions): Port from
cfg_superedge to get_cfg_edge.
* diagnostic-manager.h: Include "analyzer/supergraph.h" and
"analyzer/event-loc-info.h".
(struct pending_location): Move decl earlier in file. Replace
the existing specified ctors with 3 new ones. Add comments.
(class pending_location::fixer_for_epath): New.
(pending_location::get_location): New.
(pending_location::to_json): New decl.
(pending_location::m_snode): Drop redundant field.
(pending_location::m_event_loc_info): New field, replacing m_stmt
and m_loc.
(pending_location::m_finder): Replace with...
(pending_location::m_fixer_for_epath): ...this new field.
(make_ploc_fixer_for_epath_for_leak_diagnostic): New decl.
(saved_diagnostic::saved_diagnostic): Pass in param "ploc" by
rvalue reference and store it in m_ploc.
(saved_diagnostic::get_supernode): New.
(saved_diagnostic::m_ploc): New field, replacing m_enode,
m_snode, m_stmt, m_stmt_finder, and m_loc.
(diagnostic_manager::add_diagnostic): Pass ploc as rvalue
reference.
(diagnostic_manager::add_events_for_superedge): Delete decl.
* engine.cc: Include "gimple-predict.h" and
"analyzer/impl-sm-context.h".
(impl_region_model_context::impl_region_model_context): Drop
stmt_finder.
(impl_region_model_context::warn): Convert to...
(impl_region_model_context::warn_at): ...this.
(class impl_sm_context): Move to impl-sm-context.h.
(impl_region_model_context::get_state_map_by_name): Drop
m_stmt_finder.
(class leak_stmt_finder): Reimplement as...
(class leak_ploc_fixer_for_epath): ...this.
(make_ploc_fixer_for_epath_for_leak_diagnostic): New.
(returning_from_function_p): Update for supergraph changes.
(impl_region_model_context::on_state_leak): Port from
leak_stmt_finder to leak_ploc_fixer_for_epath.
(impl_region_model_context::on_condition): Update for
location-handling changes.
(impl_region_model_context::on_bounded_ranges): Likewise.
(impl_region_model_context::on_phi): Likewise.
(impl_region_model_context::get_pending_location_for_diag): New.
(exploded_node::status_to_str): Add status::special.
(exploded_node::get_processed_stmt): Delete.
(exploded_node::dump_dot): Elide state if we have a single
predecessor and the state hasn't changed.
(exploded_node::dump_processed_stmts): Delete.
(exploded_node::on_stmt): Delete, reimplementing in ops.cc as
gimple_stmt_op::execute_on_state, call_and_return_op::execute, and
operation::handle_on_stmt_for_state_machines.
(exploded_node::on_stmt_pre): Delete, reimplementing in ops.cc as
call_and_return_op::make.
(exploded_node::on_stmt_post): Delete.
(class call_summary_edge_info): Move to ops.cc.
(exploded_node::replay_call_summaries): Delete.
(exploded_node::replay_call_summary): Delete.
(exploded_node::on_edge): Delete.
(exploded_node::on_longjmp): Eliminate ambiguous "setjmp_point"
and "next_point" in favor of "point_before_setjmp" and
"point_after_setjmp".
(exploded_graph::unwind_from_exception): Update for changes to
program_point.
(exploded_node::on_throw): Convert "after_throw_point" to a param.
(exploded_node::on_resx): Delete.
(exploded_node::detect_leaks): Update for renaming of
supernode::return_p to supernode::exit_p, and drop stmt param of
impl_region_model_context ctor.
(dynamic_call_info_t::update_model): Delete.
(dynamic_call_info_t::add_events_to_path): Delete.
(interprocedural_call::print): New.
(interprocedural_call::get_dot_attrs): New.
(interprocedural_call::update_state): New.
(interprocedural_call::update_model): New.
(interprocedural_call::add_events_to_path): New.
(interprocedural_return::print): New.
(interprocedural_return::get_dot_attrs): New.
(interprocedural_return::update_state): New.
(interprocedural_return::update_model): New.
(interprocedural_return::add_events_to_path): New.
(rewind_info_t::add_events_to_path): Add pending_diagnostic &
param.
(exploded_edge::dump_dot_label): Drop superedge kinds. Show
op vs no-op. Flush before printing any superedge label, and
escape that label.
(exploded_edge::maybe_get_stmt): New.
(exploded_edge::maybe_get_op): New.
(stats::stats): Update for change to m_num_nodes;
(stats::log): Likewise.
(stats::dump): Likewise.
(stats::get_total_enodes): Likewise.
(strongly_connected_components::strongly_connected_components):
Update for changes to supergraph.
(strongly_connected_components::dump): Show the stack. Update for
changes to supernode.
(strongly_connected_components::to_json): Update for changes to
supergraph.
(strongly_connected_components::strong_connect): Rename "index" to
"id". Drop superedge kinds.
(worklist::key_t::cmp): Compare BB indexes before snode ids.
Drop function_point.
(exploded_graph::exploded_graph): Update stats initialization.
(tainted_args_function_info::update_model): Reimplement.
(tainted_args_function_info::add_events_to_path): Add param.
(exploded_graph::get_or_create_node): Check for recursion limit
here, rather than in program_point::on_edge and
exploded_graph::maybe_create_dynamic_call. Only merge state
for points with state_merge_at_p. Update stats tracking for
changes to supergraph. Fix wording of log of state.
(exploded_graph::get_or_create_per_call_string_data): Update for
supergraph changes.
(tainted_args_call_info::update_model): Reimplement.
(tainted_args_call_info::add_events_to_path): Add param.
(exploded_graph::process_worklist): Drop assertions that nodes
have no successors, due to some cases during unwinding exceptions.
Update call to maybe_process_run_of_before_supernode_enodes to
call to maybe_process_run_of_enodes, and only at points for
which state_merge_at_p. Reimplement "too complex" check.
(exploded_graph::maybe_process_run_of_before_supernode_enodes):
Convert to...
(exploded_graph::maybe_process_run_of_enodes): ...this. Only
consider nodes with a single successor in the supergraph and for
which that superedge supports_bulk_merge_p. Port state updates to
using operation::update_state_for_bulk_merger.
(stmt_requires_new_enode_p): Delete.
(state_change_requires_new_enode_p): Delete.
(exploded_graph::maybe_create_dynamic_call): Delete.
(class impl_path_context): Reimplement in ops.cc.
(class jump_through_null): Move to region-model.cc.
(exploded_graph::process_node): Use location_t from supernode,
rather than trying to have a stmt associated with a supernode.
Drop switch on program_point kind, instead using the operation, if any,
from the superedge.
(exploded_graph::get_or_create_function_stats): Update computation
of num_supernodes for the function.
(exploded_graph::print_bar_charts): Update for supergraph changes.
(exploded_graph::dump_stats): Likewise.
(exploded_graph::dump_states_for_supernode): Delete.
(exploded_graph::to_json): Update comment.
(exploded_path::find_stmt_backwards): Update for supergraph
reimplementation.
(exploded_path::feasible_p): Drop "last_stmt".
(feasibility_state::maybe_update_for_edge): Move most of
implementation to ops and custom_edge_infos.
(feasibility_state::update_for_stmt): Delete.
(supernode_cluster::dump_dot): Update for supernode changes.
(supernode_cluster::cmp_ptr_ptr): Likewise.
(exploded_graph::dump_exploded_nodes): Update for
location-handling changes, and for changes to supergraph
representation.
(class viz_callgraph_node): Delete
(class viz_callgraph_edge): Delete.
(class viz_callgraph): Delete.
(class viz_callgraph_cluster): Delete.
(struct viz_callgraph_traits): Delete.
(dump_callgraph): Delete.
(exploded_graph_annotator::exploded_graph_annotator): Update for
supernode::m_index becoming supernode:m_id.
(exploded_graph_annotator::add_node_annotations): Reimplement to
show enodes within the node for the supernode.
(exploded_graph_annotator::print_enode_port): New.
(exploded_graph_annotator::print_enode): Add port.
(exploded_graph_annotator::print_saved_diagnostic): Drop stmt.
(exploded_graph_annotator::m_enodes_per_snodes): Convert to...
(exploded_graph_annotator::m_enodes_per_snode_id): ...this, using
std::vector.
(maybe_dump_supergraph): New.
(impl_run_checkers): Create region_model_manager before supergraph
and pass it to supergraph ctor. Dump the original form of the
supergraph, then call fixup_locations, simplify, and sort_nodes on
the supergraph, dumping it at each stage. Drop dump_callgraph.
Replace dump to "NAME.supergraph-eg.dot" with dump to
"NAME.supergraph.N.eg.dot".
* event-loc-info.h (event_loc_info::event_loc_info): Add ctors taking
const exploded_node * and const program_point &.
* exploded-graph.h: Add include of "analyzer/region-model.h".
(impl_region_model_context::impl_region_model_context): Add default
for "stmt" param. Drop "stmt_finder" param.
(impl_region_model_context::warn): Convert to...
(impl_region_model_context::warn_at): ...this.
(impl_region_model_context::get_pending_location_for_diag): New.
(impl_region_model_context::m_stmt_finder): Drop.
(struct exploded_node::on_stmt_flags): Drop.
(exploded_node::on_stmt): Drop.
(exploded_node::on_stmt_pre): Drop.
(exploded_node::on_stmt_post): Drop.
(exploded_node::replay_call_summaries): Drop.
(exploded_node::replay_call_summary): Drop.
(exploded_node::on_edge): Drop.
(exploded_node::on_throw): Add "after_throw_point" param.
(exploded_node::on_resx): Drop.
(exploded_node::get_location): New.
(exploded_node::get_stmt): Drop.
(exploded_node::get_processed_stmt): Drop.
(exploded_node::maybe_get_stmt): New decl.
(exploded_node::maybe_get_op): New decl.
(class dynamic_call_info_t): Delete.
(class interprocedural_call): New.
(class interprocedural_return): New.
(rewind_info_t::add_events_to_path): Add pending_diagnostic &
param.
(rewind_info_t::get_setjmp_point): Replace with...
(rewind_info_t::get_point_before_setjmp): ...this...
(rewind_info_t::get_point_after_setjmp): ...and this.
(stats::m_num_nodes): Convert from an array to a plain int.
(class strongly_connected_components): Convert from index to id
throughout.
(exploded_graph::maybe_process_run_of_before_supernode_enodes):
Replace with...
(exploded_graph::maybe_process_run_of_enodes): ...this.
(exploded_graph::maybe_create_dynamic_call): Delete.
(exploded_graph::save_diagnostic): Drop stmt_finder param.
(exploded_graph::dump_states_for_supernode): Drop.
(exploded_graph::m_PK_AFTER_SUPERNODE_per_snode): Drop.
(class feasibility_problem): Drop "m_last_stmt".
(feasibility_state::update_for_stmt): Drop.
(feasibility_state::get_model): Add non-const accessor.
(feasibility_state::get_snodes_visited): New accessor.
(class stmt_finder): Drop.
* feasible-graph.cc (feasible_node::dump_dot): Drop call to
dump_processed_stmts.
(feasible_node::get_state_at_stmt): Drop.
* impl-sm-context.h: New file, adapted from material in engine.cc.
* infinite-loop.cc
(perpetual_start_cfg_edge_event::perpetual_start_cfg_edge_event):
Add "op" param.
(perpetual_start_cfg_edge_event::print_desc): Use m_op to describe
condition.
(looping_back_event::looping_back_event): Add "op" param.
(infinite_loop_diagnostic::maybe_add_custom_events_for_superedge):
Convert to...
(infinite_loop_diagnostic::maybe_add_custom_events_for_eedge):
...this.
(infinite_loop_diagnostic::add_final_event): Port from
cfg_superedge to get_any_cfg_edge and operations. Update for
location-handling changes.
(get_in_edge_back_edge): Port from cfg_superedge to
get_any_cfg_edge.
(starts_infinite_loop_p): Update for location-handling changes.
(exploded_graph::detect_infinite_loops): Remove redundant params.
* infinite-recursion.cc
(infinite_recursion_diagnostic::add_final_event): Update for
location-handling changes.
(infinite_recursion_diagnostic::check_valid_fpath_p): Drop gimple
param.
(infinite_recursion_diagnostic::fedge_uses_conjured_svalue_p):
Port from cfg_superedge to operations.
(is_entrypoint_p): Update for supergraph changes.
(exploded_graph::detect_infinite_recursion): Update for
location-handling changes.
* kf-lang-cp.cc (kf_operator_new::impl_call_pre): Split out
code to handle placement-new into...
(kf_operator_new::check_any_preconditions): ...this...
(kf_operator_new::get_sized_region_for_placement_new): ...and
this.
* ops.cc: New file, albeit with material adatpted from old
implementation.
* ops.h: Likewise.
* pending-diagnostic.cc (pending_diagnostic::add_call_event): Add
gcall param. Update for changes to location-handling.
* pending-diagnostic.h
(pending_diagnostic::maybe_add_custom_events_for_superedge):
Convert to...
(pending_diagnostic::maybe_add_custom_events_for_eedge): ...this.
(pending_diagnostic::add_call_event): Add "call_stmt" param.
(pending_diagnostic::check_valid_fpath_p): Drop stmt param.
* program-point.cc (point_kind_to_string): Delete.
(function_point::function_point): Delete.
(function_point::print): Delete.
(function_point::hash): Delete.
(function_point::get_function): Delete.
(function_point::get_stmt): Delete.
(function_point::get_location): Delete.
(function_point::final_stmt_p): Delete.
(function_point::from_function_entry): Delete.
(function_point::before_supernode): Delete.
(function_point::print_source_line): Convert to...
(program_point::print_source_line): ...this.
(program_point::print): Reimplement.
(program_point::to_json): Likewise.
(program_point::push_to_call_stack): Delete.
(program_point::hash): Reimplement.
(program_point::get_function_at_depth): Likewise.
(program_point::on_edge): Delete.
(function_point::cmp_within_supernode_1): Delete.
(function_point::cmp_within_supernode): Delete.
(function_point::cmp): Delete.
(function_point::cmp_ptr): Delete.
(function_point::next_stmt): Delete.
(function_point::get_next): Delete.
(program_point::origin): Update.
(program_point::from_function_entry): Update.
(program_point::get_next): Delete.
(selftest::test_function_point_equality): Delete.
(selftest::test_function_point_ordering): Delete.
(selftest::test_program_point_equality): Update for changes to
program_point.
(selftest::analyzer_program_point_cc_tests): Don't call deleted
function_point tests.
* program-point.h: Include "analyzer/supergraph.h".
(class exploded_graph): Drop forward decl.
(enum point_kind): Drop.
(point_kind_to_string): Drop decl.
(class function_point): Delete.
(program_point::program_point): Take a const supernode *
rather than a const function_point &.
(program_point::print_source_line): New decl.
(program_point::operator==): Update.
(program_point::get_function_point): Drop.
(program_point::get_supernode): Reimplement.
(program_point::get_function): Reimplement.
(program_point::get_fndecl): Reimplement.
(program_point::get_stmt): Drop.
(program_point::get_location): Reimplement.
(program_point::get_kind): Drop.
(program_point::get_from_edge): Drop.
(program_point::get_stmt_idx): Drop.
(program_point::get_stack_depth): Update.
(program_point::state_merge_at_p): New.
(program_point::before_supernode): Drop.
(program_point::before_stmt): Drop.
(program_point::after_supernode): Drop.
(program_point::empty): Drop.
(program_point::deleted): Drop.
(program_point::on_edge): Drop.
(program_point::push_to_call_stack): Drop.
(program_point::next_stmt): Drop.
(program_point::get_next): Drop.
(program_point::m_function_point): Replace with...
(program_point::m_snode): ...this.
* program-state.cc (program_state::on_edge): Delete.
(program_state::push_call): Delete.
(program_state::returning_call): Delete.
(program_state::prune_for_point): Port from function_point to
supernode. Drop stmt param to impl_region_model_context ctor.
(selftest::test_sm_state_map): Update for engine borrowing rather
owning the region_model_manager.
(selftest::test_program_state_1): Likewise.
(selftest::test_program_state_2): Likewise.
(selftest::test_program_state_merging): Likewise.
(selftest::test_program_state_merging_2): Likewise.
* program-state.h (program_state::push_call): Delete decl.
(program_state::returning_call): Delete decl.
(program_state::on_edge): Delete decl.
* region-model-manager.cc (region_model_manager::maybe_fold_unaryop):
Only fold constants if we have a type.
(region_model_manager::get_or_create_widening_svalue): Port from
function_point to supernode.
* region-model-manager.h
(region_model_manager::get_or_create_widening_svalue): Likewise.
* region-model.cc
(poisoned_value_diagnostic::check_valid_fpath_p): Drop code for
handling function_points within an snode.
(exception_thrown_from_unrecognized_call::add_events_to_path): Add
pending_diagnostic param.
(class jump_through_null): Move here from engine.cc.
(region_model::on_call_pre): Check for jump through null here,
rather than in exploded_graph::process_node.
(region_model::on_setjmp): Add superedge param and pass it to
setjmp_record ctor.
(region_model::handle_phi): Delete, in favor of
phis_for_edge_op::update_state in ops.cc.
(region_model::update_for_phis): Likewise.
(region_model::maybe_update_for_edge): Delete.
(region_model::update_for_call_superedge): Delete.
(region_model::update_for_return_superedge): Delete.
(region_model::apply_constraints_for_gcond): Reimplement in ops.cc as
gcond_edge_op::apply_constraints.
(has_nondefault_case_for_value_p): Move to ops.cc.
(has_nondefault_cases_for_all_enum_values_p): Move to ops.cc
(region_model::apply_constraints_for_gswitch): Reimplement in
ops.cc as switch_case_op::apply_constraints.
(class rejected_eh_dispatch): Move to ops.cc.
(exception_matches_type_p): Move to ops.cc.
(matches_any_exception_type_p): Move to ops.cc.
(region_model::apply_constraints_for_eh_dispatch): Reimplement in
ops.cc as eh_dispatch_edge_op::apply_constraints.
(region_model::apply_constraints_for_eh_dispatch_try): Reimplement
in ops.cc as eh_dispatch_try_edge_op::apply_eh_constraints.
(region_model::apply_constraints_for_eh_dispatch_allowed):
Reimplement in ops.cc as
eh_dispatch_allowed_edge_op::apply_eh_constraints.
(region_model::apply_constraints_for_ggoto): Reimplement in ops.cc
as ggoto_edge_op::apply_constraints.
(caller_context::warn): Replace with...
(caller_context::get_pending_location_for_diag): ...this.
(region_model::get_or_create_region_for_heap_alloc): Fix
indentation.
(region_model_context::warn): New, replacing vfunc with
shared code that calls get_pending_location_for_diag and warn_at
vfuncs.
(engine::engine): Borrow m_mgr rather than own it.
(seldtest::test_state_merging): Update test for ptrs to different
base regions becoming unmergeable.
(selftest::test_widening_constraints): Port from function_point to
supernode.
* region-model.h: Include "analyzer/diagnostic-manager.h".
(region_model::on_setjmp): Add superedge param.
(region_model::void update_for_phis): Drop decl.
(region_model::handle_phi): Drop decl.
(region_model::maybe_update_for_edge): Drop decl.
(region_model::apply_constraints_for_eh_dispatch_try): Drop decl.
(region_model::apply_constraints_for_eh_dispatch_allowed): Drop
decl.
(region_model::update_for_call_superedge): Drop decl.
(region_model::update_for_return_superedge): Drop decl.
(region_model::apply_constraints_for_gcond): Drop decl.
(region_model::apply_constraints_for_gswitch): Drop decl.
(region_model::apply_constraints_for_ggoto): Drop decl.
(region_model::apply_constraints_for_eh_dispatch): Drop decl.
(region_model_context::warn): Convert from vfunc to func.
(region_model_context::get_pending_location_for_diag): New vfunc.
(region_model_context::warn_at): New vfunc.
(class noop_region_model_context): Update for changes to
region_model_context.
(class region_model_context_decorator): Likewise.
(class annotating_context): Likewise.
(struct model_merger): Port from function_point to supernode.
(class engine): Borrow m_mgr rather than own it.
(class test_region_model_context): Update for changes to
region_model_context.
* region.cc (frame_region::get_region_for_local): Update for
change to supergraph.
* sm-fd.cc: Drop redundant params throughout. Pass stmt
rather than node to the various on_ calls.
* sm-file.cc: Drop redundant params throughout.
(register_known_file_functions): Register "*_unlocked" versions of
functions that I'd missed.
* sm-malloc.cc: Drop redundant params throughout.
(deref_before_check::loop_header_p): Reimplement cfg_superedge
check.
(malloc_state_machine::on_stmt): Move attribute-handling to...
(malloc_state_machine::check_call_preconditions): ...this new
function.
(maybe_complain_about_deref_before_check): Use
sm_ctxt.get_emission_location when checking for inlining.
* sm-pattern-test.cc: Drop redundant params throughout.
* sm-sensitive.cc: Likewise.
* sm-signal.cc: Likewise.
* sm-taint.cc: Likewise.
* sm.cc: Fix unused param warnings.
* sm.h: Include "analyzer/analyzer-logging.h". Drop redundant
params throughout.
(state_machine::check_call_preconditions): New vfunc.
(sm_context::get_state): Drop "stmt" args.
(sm_context::set_next_state): Likewise.
(sm_context::on_transition): Drop "stmt" and "node" args.
(sm_context::warn): Likewise.
(sm_context::get_emission_location): New vfunc.
* state-purge.cc: Define INCLUDE_SET.
(class gimple_op_visitor): Replace function_point and function
with superedge.
(state_purge_map::state_purge_map): Iterate through ops on edges,
rather than on stmts in supernodes.
(state_purge_map::on_duplicated_node): New.
(state_purge_map::get_or_create_data_for_decl): Use supernode
rather than function_point.
(state_purge_per_ssa_name::state_purge_per_ssa_name): Likewise.
(state_purge_per_ssa_name::needed_at_point_p): Replace with...
(state_purge_per_ssa_name::needed_at_supernode_p): ...this.
(state_purge_per_ssa_name::before_use_stmt): Delete.
(state_purge_per_ssa_name::add_to_worklist): Use supernode rather
than function_point.
(name_used_by_phis_p): Delete.
(state_purge_per_ssa_name::process_point): Replace with...
(state_purge_per_ssa_name::process_supernode): ...this.
(state_purge_per_ssa_name::on_duplicated_node): New.
(state_purge_per_decl::state_purge_per_decl): Use supernode rather
than function_point.
(state_purge_per_decl::add_needed_at): Likewise.
(state_purge_per_decl::add_pointed_to_at): Likewise.
(state_purge_per_decl::process_worklists): Likewise.
(state_purge_per_decl::add_to_worklist): Likewise.
(state_purge_per_decl::process_point_backwards): Replace with...
(state_purge_per_decl::process_supernode_backwards): ...this.
(state_purge_per_decl::process_point_forwards): Replace with...
(state_purge_per_decl::process_supernode_forwards): ...this.
(state_purge_per_decl::needed_at_point_p): Replace with...
(state_purge_per_decl::needed_at_supernode_p): ...this.
(state_purge_per_decl::on_duplicated_node): New.
(print_vec_of_names): Drop "within_table" param.
(state_purge_annotator::add_stmt_annotations): Drop.
(state_purge_annotator::add_node_annotations): Reimplement.
* state-purge.h: Convert throughout from function_point to
supernode.
(state_purge_map::on_duplicated_node): New decl.
(state_purge_per_ssa_name::on_duplicated_node): Likewise.
(state_purge_per_decl::on_duplicated_node): Likewise.
* store.cc (needs_loop_replay_fixup_p): New.
(store::loop_replay_fixup): Use it rather than checking for
SK_WIDENING.
* supergraph-fixup-locations.cc: New file.
* supergraph-manipulation.h: New file.
* supergraph-simplify.cc: New file.
* supergraph-sorting.cc: New file.
* supergraph.cc: Define INCLUDE_DEQUE. Drop include of
"tree-dfa.h". Include "diagnostics/file-cache.h"
and "analyzer/exploded-graph.h".
(supergraph_call_edge): Delete.
(control_flow_stmt_p): New.
(supergraph::supergraph): Add "mgr" param. Initialize
m_next_snode_id. Reimplement.
(supergraph::populate_for_basic_block): New.
(supergraph::dump_dot_to_pp): Add auto_cfun sentinel.
Split up nodes using loop information from the original CFG,
then by basic block. Call the node_annotator's
add_extra_objects vfunc.
(supergraph::dump_dot_to_gv_for_loop): New.
(supergraph::dump_dot_to_gv_for_bb): New, based on code in
dump_dot_to_pp.
(supergraph::add_node): Drop "returning_call" and "phi_nodes"
params. Add logger param and logging. Use m_next_snode_id to
allow for node deletion.
(supergraph::add_cfg_edge): Delete.
(supergraph::add_call_superedge): Delete.
(supergraph::add_return_superedge): Delete.
(supergraph::delete_nodes): New.
(supergraph::add_sedges_for_cfg_edge): New.
(supernode::dump_dot): Drop output cluster, moving
add_node_annotations to within the dot node. Show any SCC id.
Show m_preserve_p and m_state_merger_node. Update for renaming of
supernode::return_p to supernode::exit_p. Highlight nodes without
source location information. Show m_loc and m_stmt_loc. Show
source lines, with color for highlight.
(supernode::dump_dot_id): Update.
(supernode::to_json): Update.
(supernode::get_start_location): Delete.
(supernode::get_end_location): Delete.
(supernode::get_stmt_index): Delete.
(supernode::get_label): Delete.
(edge_kind_to_string): Delete.
(superedge::dump): Update for supernode::m_index becoming m_id.
(superedge::dump_dot): Drop ltail/lhead attrs. Flush after
dumping the label.
(superedge::to_json): Reimplement.
(superedge::get_any_cfg_edge): Delete.
(superedge::get_any_callgraph_edge): Delete.
(superedge::preserve_p): New.
(superedge::supports_bulk_merge_p): New.
(cfg_superedge::dump_label_to_pp): Delete.
(superedge::dump_label_to_pp): New.
(cfg_superedge::get_phi_arg_idx): Delete.
(cfg_superedge::get_phi_arg): Delete.
(switch_cfg_superedge::switch_cfg_superedge): Delete.
(switch_cfg_superedge::dump_label_to_pp): Delete.
(switch_cfg_superedge::implicitly_created_default_p): Delete.
(get_catch): Move to ops.cc.
(eh_dispatch_cfg_superedge::make): Delete in favor of
eh_dispatch_edge_op::make.
(eh_dispatch_cfg_superedge::eh_dispatch_cfg_superedge): Delete.
(eh_dispatch_cfg_superedge::get_eh_status): Delete.
(eh_dispatch_try_cfg_superedge::dump_label_to_pp): Delete.
(eh_dispatch_try_cfg_superedge::apply_constraints): Delete.
(eh_dispatch_allowed_cfg_superedge::eh_dispatch_allowed_cfg_superedge):
Delete.
(eh_dispatch_allowed_cfg_superedge::dump_label_to_pp): Delete.
(eh_dispatch_allowed_cfg_superedge::apply_constraints): Delete.
(callgraph_superedge::dump_label_to_pp): Delete.
(callgraph_superedge::get_callee_function): Delete.
(callgraph_superedge::get_caller_function): Delete
(callgraph_superedge::get_callee_decl): Delete
(callgraph_superedge::get_call_stmt): Delete
(callgraph_superedge::get_caller_decl): Delete
(callgraph_superedge::get_arg_for_parm): Delete in favor of
call_and_return_op::get_arg_for_parm in ops.cc.
(callgraph_superedge::get_parm_for_arg): Delete in favor of
call_and_return_op::get_parm_for_arg in ops.cc.
(callgraph_superedge::map_expr_from_caller_to_callee): Delete in
favor of call_and_return_op::map_expr_from_caller_to_callee in
ops.cc.
(callgraph_superedge::map_expr_from_callee_to_caller): Delete in
favor of call_and_return_op::map_expr_from_callee_to_caller in
ops.cc.
* supergraph.h: Include "cfgloop.h" and "analyzer/ops.h".
(enum edge_kind): Delete.
(struct supergraph_traits::dump_args_t): Add m_eg.
(class supergraph): Rewrite leading comment.
(supergraph::supergraph): Add "mgr" param.
(supergraph::get_node_for_function_entry): Reimplement.
(supergraph::get_node_for_function_exit): Reimplement.
(supergraph::get_node_for_block): Convert to...
(supergraph::get_initial_node_for_block): ...this.
(supergraph::get_caller_next_node): Delete.
(supergraph::get_edge_for_call): Delete.
(supergraph::get_edge_for_return): Delete.
(supergraph::get_intraprocedural_edge_for_call): Delete.
(supergraph::get_edge_for_cfg_edge): Delete.
(supergraph::get_supernode_for_stmt): Delete.
(supergraph::get_final_node_for_block): New.
(supergraph::get_supernode_for_stmt): New.
(supergraph::get_superedge_for_phis): New.
(supergraph::get_node_by_index): Delete.
(supergraph::add_node): Drop "returning_call" and "phi_nodes"
params. Add logger param.
(supergraph::add_cfg_edge): Delete.
(supergraph::add_call_superedge): Delete.
(supergraph::add_return_superedge): Delete.
(supergraph::log_stats): New decl.
(supergraph::delete_nodes): New decl.
(supergraph::fixup_locations): New decl.
(supergraph::simplify): New decl.
(supergraph::sort_nodes): New decl.
(supergraph::populate_for_basic_block): New decl.
(supergraph::add_sedges_for_cfg_edge): New decl.
(supergraph::dump_dot_to_gv_for_loop): New decl.
(supergraph::dump_dot_to_gv_for_bb): New decl.
(supergraph::reorder_nodes_and_ids): New decl.
(supergraph::bb_to_node_t): Make private.
(supergraph::m_bb_to_initial_node): Make private.
(supergraph::m_bb_to_final_node): Make private.
(supergraph::cgraph_edge_to_node_t): Delete typedef.
(supergraph::m_cgraph_edge_to_caller_prev_node): Delete.
(supergraph::m_cgraph_edge_to_caller_next_node): Delete.
(supergraph::cfg_edge_to_cfg_superedge_t): Delete typedef.
(supergraph::m_cfg_edge_to_cfg_superedge): Delete.
(supergraph::cgraph_edge_to_call_superedge_t): Delete typedef.
(supergraph::m_cgraph_edge_to_call_superedge): Delete
(supergraph::cgraph_edge_to_return_superedge_t): Delete typedef.
(supergraph::m_cgraph_edge_to_return_superedge): Delete.
(supergraph::cgraph_edge_to_intraproc_superedge_t): Delete
typedef.
(supergraph::m_cgraph_edge_to_intraproc_superedge): Delete.
(supergraph::stmt_to_node_t): Delete typedef.
(supergraph::m_stmt_to_node_t): Replace with...
(supergraph::m_node_for_stmt): ...this.
(supergraph::m_edges_for_phis): New field.
(supergraph::m_next_snode_id): New field.
(supergraph::m_snode_by_id): New field.
(supernode::supernode): Drop "returning_call" and "phi_nodes"
params. Convert "index" to "id". Update for changes to fields.
(supernode::return_p): Rename for clarity to...
(supernode::exit_p): ...this.
(supernode::get_start_location): Delete.
(supernode::get_end_location): Delete.
(supernode::start_phis): Delete.
(supernode::get_returning_call): Delete.
(supernode::print): New.
(supernode::get_last_stmt): Delete.
(supernode::get_final_call): Delete.
(supernode::get_stmt_index): Delete.
(supernode::get_location): New.
(supernode::get_label): Convert to trivial accessor.
(supernode::preserve_p): New.
(supernode::m_returning_call): Drop field.
(supernode::m_phi_nodes): Drop field.
(supernode::m_stmts): Drop field.
(supernode::m_index): Replace with...
(supernode::m_id): ...this.
(supernode::m_loc): New field.
(supernode::m_stmt_loc): New field.
(supernode::m_original_id): New field.
(supernode::m_label): New field.
(supernode::m_preserve_p): New field.
(supernode::m_state_merger_node): New field.
(class superedge): Update leading comment.
(superedge::superedge): Make public rather than protected. Drop
"kind" param. Add "op" and "cfg_edge" params. Assert that edge
is intraprocedural.
(superedge::m_kind): Drop field.
(superedge::m_op): New field.
(superedge::m_cfg_edge): New field.
(superedge::dump_label_to_pp): Make non-virtual.
(superedge::get_op): New.
(superedge::set_op): New.
(superedge::get_kind): Drop.
(superedge::get_dest_snode): New accessor.
(superedge::dyn_cast_cfg_superedge): Delete.
(superedge::dyn_cast_switch_cfg_superedge): Delete
(superedge::dyn_cast_eh_dispatch_cfg_superedge): Delete
(superedge::dyn_cast_eh_dispatch_try_cfg_superedge): Delete
(superedge::dyn_cast_eh_dispatch_allowed_cfg_superedge): Delete
(superedge::dyn_cast_callgraph_superedge): Delete
(superedge::dyn_cast_callgraph_superedge): Delete
(superedge::dyn_cast_call_superedge): Delete
(superedge::dyn_cast_call_superedge): Delete
(superedge::dyn_cast_return_superedge): Delete
(superedge::dyn_cast_return_superedge): Delete
(superedge::get_any_cfg_edge): Convert to trivial accessor.
(superedge::get_any_callgraph_edge): Drop.
(superedge::preserve_p): New.
(superedge::supports_bulk_merge_p): New.
(class callgraph_superedge): Drop.
(is_a_helper <const callgraph_superedge *>::test): Drop.
(class call_superedge): Drop.
(is_a_helper <const call_superedge *>::test): Drop.
(class return_superedge): Drop.
(is_a_helper <const return_superedge *>::test): Drop.
(class cfg_superedge): Drop.
(class switch_cfg_superedge): Drop.
(is_a_helper <const switch_cfg_superedge *>::test): Drop.
(class eh_dispatch_cfg_superedge): Drop.
(is_a_helper <const eh_dispatch_cfg_superedge *>::test): Drop.
(class eh_dispatch_try_cfg_superedge): Drop.
(is_a_helper <const eh_dispatch_try_cfg_superedge *>::test): Drop.
(class eh_dispatch_allowed_cfg_superedge): Drop.
(is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::test):
Drop.
(dot_annotator::~dot_annotator): Use "= default;".
(dot_annotator::add_node_annotations): Drop return value and
"within_table" param.
(dot_annotator::add_stmt_annotations): Drop.
(dot_annotator::add_after_node_annotations): Drop.
(dot_annotator::add_extra_objects): New.
(supergraph_call_edge): Delete decl.
(get_ultimate_function_for_cgraph_edge): Delete decl.
* svalue.cc (svalue::can_merge_p): Reject attempts to merge
pointers that point to different base regions, except for the case
where both are string literals. Update for point change in
widening_svalue.
(svalue::cmp_ptr): Update for point change to widening_svalue.
(widening_svalue::dump_to_pp): Likewise.
(widening_svalue::print_dump_widget_label): Likewise.
* svalue.h (struct setjmp_record): Add m_sedge.
(class widening_svalue): Replace function_point m_point with
const supernode *m_snode throughout.
* varargs.cc (va_list_state_machine::on_stmt): Drop redundant
param.
(va_list_state_machine::on_va_start): Likewise. Update for change
to get_state.
(va_list_state_machine::check_for_ended_va_list): Likewise.
(va_list_state_machine::on_va_copy): Likewise.
(va_list_state_machine::on_va_arg): Likewise.
(va_list_state_machine::on_va_end): Likewise.
(va_arg_diagnostic::add_call_event): Update for changes to
location-tracking.
gcc/testsuite/ChangeLog:
PR analyzer/122003
* c-c++-common/analyzer/allocation-size-multiline-1.c: Update for split
of region creation events.
* c-c++-common/analyzer/bzip2-arg-parse-1.c: Drop test for enode
merging. Add -Wno-analyzer-too-complex.
* c-c++-common/analyzer/coreutils-cksum-pr108664.c: Add
-Wno-analyzer-symbol-too-complex. Add dg-bogus for false +ve seen
during patch development.
* c-c++-common/analyzer/coreutils-group_number.c: New test.
* c-c++-common/analyzer/data-model-20.c: Mark warnings as xfail.
* c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c:
Add xfails.
* c-c++-common/analyzer/dot-output.c: Update for changes to dumps.
* c-c++-common/analyzer/fd-symbolic-socket.c: Update for
improvements to locations of leaks.
* c-c++-common/analyzer/fibonacci.c: Update regex.
* c-c++-common/analyzer/flex-with-call-summaries.c: Add xfail.
* c-c++-common/analyzer/flex-without-call-summaries.c: Add
-Wno-analyzer-symbol-too-complex. Add xfail.
* c-c++-common/analyzer/infinite-recursion-5.c: Disable cases that
now explode the analysis.
* c-c++-common/analyzer/infinite-recursion-pr108524-2.c: Remove
xfail.
* c-c++-common/analyzer/invalid-shift-1.c: Remove xfails with c++26.
* c-c++-common/analyzer/ipa-callbacks-1.c: New test.
* c-c++-common/analyzer/loop-4.c: Expect incorrect UNKNOWN within
loop. Update expected number of enodes.
* c-c++-common/analyzer/loop-n-down-to-1-by-1.c: Expect incorrect
UNKNOWN within loop.
* c-c++-common/analyzer/loop.c: Drop xfail.
* c-c++-common/analyzer/out-of-bounds-coreutils.c: Expect infinite
loop warning.
* c-c++-common/analyzer/paths-4.c: Update expected number of
enodes.
* c-c++-common/analyzer/pr94362-1.c: Drop -Wno-analyzer-too-complex.
* c-c++-common/analyzer/pr94851-2.c: Add xfail.
* c-c++-common/analyzer/pr96650-1-notrans.c: Add
-Wno-analyzer-too-complex.
* c-c++-common/analyzer/pr98628.c: Likewise.
* c-c++-common/analyzer/pr99774-1.c: Likewise.
* c-c++-common/analyzer/pragma-2.c: Expect double-free warning.
* c-c++-common/analyzer/realloc-1.c: Move expected location of
leak from trailing "}" to realloc call.
* c-c++-common/analyzer/sock-1.c: Add -fno-exceptions.
* c-c++-common/analyzer/sprintf-2.c: Add __attribute__ nonnull to
decl.
* c-c++-common/analyzer/sprintf-concat.c: Move expected location
of leak of p from sprintf to trailing "}".
* c-c++-common/analyzer/stdarg-sentinel-1.c: Drop
-Wno-analyzer-too-complex.
* c-c++-common/analyzer/strncpy-1.c: Add __attribute__ nonnull to
decl.
* c-c++-common/analyzer/strstr-1.c: Likewise.
* g++.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
* g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C:
Update expected messages.
* g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C:
Likewise.
* g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C: Likewise.
* g++.dg/analyzer/pr94028.C: Move expected location of leak
warning to where return value of f is discarded within m.
* g++.dg/analyzer/pr96641.C: Expect infinite recursion warning.
* gcc.dg/analyzer/CWE-131-examples.c: Add
-Wno-analyzer-too-complex.
* gcc.dg/analyzer/abs-1.c (test_2): Fix return type.
* gcc.dg/analyzer/analyzer-decls.h: Reformat. Add
__attribute__ ((nothrow)) to all functions.
* gcc.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
* gcc.dg/analyzer/boxed-malloc-1.c: Fix return types.
* gcc.dg/analyzer/call-summaries-2.c: Likewise.
* gcc.dg/analyzer/combined-conditionals-1.c: Likewise.
* gcc.dg/analyzer/compound-assignment-2.c: Expect warning about
missing return.
* gcc.dg/analyzer/compound-assignment-3.c: Likewise.
* gcc.dg/analyzer/conditionals-3.c: Fix return type.
* gcc.dg/analyzer/data-model-1.c: Likewise.
* gcc.dg/analyzer/data-model-15.c: Likewise.
* gcc.dg/analyzer/data-model-17.c: Likewise.
* gcc.dg/analyzer/data-model-20a.c: Remove xfail from bogus leak.
* gcc.dg/analyzer/data-model-7.c: Fix return type.
* gcc.dg/analyzer/doom-d_main-IdentifyVersion.c: Add xfail to some
of the leak msgs.
* gcc.dg/analyzer/doom-s_sound-pr108867.c: Add xfail.
* gcc.dg/analyzer/edges-1.c: Update for improvements to location
of leak.
* gcc.dg/analyzer/error-1.c: Fix return type.
* gcc.dg/analyzer/explode-1.c: Drop xfail. Expect uninit and
double-free warnings.
* gcc.dg/analyzer/explode-2.c: Add xfail.
* gcc.dg/analyzer/explode-3.c: Drop xfail. Expect uninit and
double-free warnings.
* gcc.dg/analyzer/fd-datagram-socket.c: Move expected location of
leaks to closing "}"s.
* gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c: Add
false +ve leak message, due to not considering that program is
about to exit.
* gcc.dg/analyzer/fd-stream-socket.c: Move expected location of
leaks to closing "}"s.
* gcc.dg/analyzer/malloc-1.c: Fix return types.
* gcc.dg/analyzer/malloc-many-paths-2.c: Likewise.
* gcc.dg/analyzer/malloc-paths-10.c: Likewise.
* gcc.dg/analyzer/malloc-vs-local-4.c: Likewise.
* gcc.dg/analyzer/memset-CVE-2017-18549-1.c: Likewise.
* gcc.dg/analyzer/null-deref-pr102671-1.c: Enable
-fanalyzer-call-summaries.
* gcc.dg/analyzer/null-deref-pr102671-2.c: Remove xfail.
* gcc.dg/analyzer/pr101143.c: Fix return type.
* gcc.dg/analyzer/pr101837.c: Fix return type. Add warning about
missing return.
* gcc.dg/analyzer/pr101983-not-main.c: Fix return type.
* gcc.dg/analyzer/pr103892.c: Enable -fanalyzer-call-summaries.
* gcc.dg/analyzer/pr104224.c: Add xfails.
* gcc.dg/analyzer/pr104434-nonconst.c: Likewise.
* gcc.dg/analyzer/pr93032-mztools-signed-char.c: Increase
exploration limits by a factor of 5.
* gcc.dg/analyzer/pr93032-mztools-unsigned-char.c: Likewise.
* gcc.dg/analyzer/pr93355-localealias-feasibility-2.c: Fix return type.
* gcc.dg/analyzer/pr93355-localealias.c: Add xfail. Add expected
leak true +ve and uninit false +ve.
* gcc.dg/analyzer/pr94579.c: Add warning about missing return.
* gcc.dg/analyzer/pr98599-a.c: Add missing return stmts.
* gcc.dg/analyzer/pr99771-1.c: Fix expected locations of leaks.
* gcc.dg/analyzer/pr99774-2.c: Likewise.
* gcc.dg/analyzer/sensitive-1.c: Fix return types.
* gcc.dg/analyzer/state-diagram-1-sarif.py: Update.
* gcc.dg/analyzer/stdarg-1.c
(__analyzer_test_not_enough_args_2_middle): Add test coverage for
wording of call event with variadic args.
* gcc.dg/analyzer/strcmp-1.c: Fix return types.
* gcc.dg/analyzer/strcpy-1.c: Likewise.
* gcc.dg/analyzer/switch-enum-taint-1.c: Add warning about missing
return.
* gcc.dg/analyzer/switch.c: Fix return types.
* gcc.dg/analyzer/taint-assert.c: Likewise.
* gcc.dg/analyzer/taint-write-offset-1.c: Likewise.
* gcc.dg/analyzer/torture/analyzer-torture.exp: Drop
-fanalyzer-call-summaries.
* gcc.dg/analyzer/torture/boxed-ptr-1.c: Fix return type.
* gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c: Add
-Wno-analyzer-too-complex.
* gcc.dg/analyzer/torture/loop-inc-ptr-1.c: Skip at -O3 to avoid
changes to enode count.
* gcc.dg/analyzer/torture/pr102225.c: Consolidate on one line to
avoid caring about precise location of leak warning.
* gcc.dg/analyzer/torture/pr93379.c: Skip on -fno-fat-lto-objects.
Add warning about uninit.
* gcc.dg/analyzer/torture/stdarg-4.c: Replace UNKNOWN with
symbolic sum of params.
* gcc.dg/analyzer/untracked-1.c: Fix return type.
* gcc.dg/analyzer/use-after-free.c: Likewise.
* gcc.dg/analyzer/zlib-3.c: Add xfails.
* gcc.dg/plugin/analyzer_cpython_plugin.cc
(class refcnt_stmt_finder): Eliminate.
(check_refcnt): ...in favor of a call to
make_ploc_fixer_for_epath_for_leak_diagnostic.
* gcc.dg/plugin/analyzer_gil_plugin.cc: Update for
location-handling changes.
* gcc.dg/plugin/infoleak-CVE-2011-1078-1.c: Add missing
"return 0;".
* gcc.dg/plugin/infoleak-CVE-2011-1078-2.c: Fix return types.
* gcc.dg/plugin/infoleak-CVE-2017-18549-1.c: Likewise.
* gdc.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
* gfortran.dg/analyzer/analyzer.exp: Likewise.
* gfortran.dg/analyzer/uninit-pr63311.f90: Add
-Wno-analyzer-too-complex.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
analyzer/kf-analyzer.o \
analyzer/kf-lang-cp.o \
analyzer/known-function-manager.o \
+ analyzer/ops.o \
analyzer/pending-diagnostic.o \
analyzer/program-point.o \
analyzer/program-state.o \
analyzer/state-purge.o \
analyzer/store.o \
analyzer/supergraph.o \
+ analyzer/supergraph-fixup-locations.o \
+ analyzer/supergraph-simplify.o \
+ analyzer/supergraph-sorting.o \
analyzer/svalue.o \
analyzer/symbol.o \
analyzer/trimmed-graph.o \
}
}
+class log_nesting_level
+{
+public:
+ log_nesting_level (logger *logger, const char *fmt, ...)
+ ATTRIBUTE_GCC_DIAG(3, 4);
+ ~log_nesting_level ();
+
+private:
+ logger *m_logger;
+};
+
+inline
+log_nesting_level::log_nesting_level (logger *logger, const char *fmt, ...)
+: m_logger (logger)
+{
+ if (logger)
+ {
+ va_list ap;
+ va_start (ap, fmt);
+
+ logger->start_log_line ();
+ logger->log_va_partial (fmt, &ap);
+ logger->end_log_line ();
+
+ logger->inc_indent ();
+
+ va_end (ap);
+ }
+}
+
+
+/* The destructor for log_nesting_level; essentially the opposite of
+ the constructor. */
+
+inline
+log_nesting_level::~log_nesting_level ()
+{
+ if (m_logger)
+ m_logger->dec_indent ();
+}
+
/* A log_user is something that potentially uses a logger (which could be
nullptr).
return is_named_call_p (fndecl, "__cxa_rethrow");
}
+bool
+is_cxa_end_catch_p (const gcall &call)
+{
+ tree fndecl = gimple_call_fndecl (&call);
+ if (!fndecl)
+ return false;
+
+ return is_named_call_p (fndecl, "__cxa_end_catch");
+}
+
/* For a CALL that matched is_special_named_call_p or is_named_call_p for
some name, return a name for the called function suitable for use in
diagnostics (stripping the leading underscores). */
Add extra annotations to diagrams.
fanalyzer-fine-grained
-Common Var(flag_analyzer_fine_grained) Init(0)
-Avoid combining multiple statements into one exploded edge.
+Common Ignore
+Does nothing. Preserved for backward compatibility.
fanalyzer-feasibility
Common Var(flag_analyzer_feasibility) Init(1)
Common Var(flag_analyzer_show_duplicate_count) Init(0)
Issue a note when diagnostics are deduplicated.
+fanalyzer-simplify-supergraph
+Common Var(flag_analyzer_simplify_supergraph) Init(1)
+Simplify the supergraph before analyzing it.
+
fanalyzer-state-purge
Common Var(flag_analyzer_state_purge) Init(1)
Purge unneeded state during analysis.
Dump various analyzer internals to stderr.
fdump-analyzer-callgraph
-Common RejectNegative Var(flag_dump_analyzer_callgraph)
-Dump analyzer-specific call graph information to a SRCFILE.callgraph.dot file.
+Common Ignore
+Does nothing. Preserved for backward compatibility.
fdump-analyzer-exploded-graph
Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
const widening_svalue *widening_sval = (const widening_svalue *)sval;
return mgr.get_or_create_widening_svalue
(NULL_TREE,
- widening_sval->get_point (),
+ widening_sval->get_snode (),
strip_types (widening_sval->get_base_svalue (), mgr),
strip_types (widening_sval->get_iter_svalue (), mgr));
}
#ifndef GCC_ANALYZER_CALL_DETAILS_H
#define GCC_ANALYZER_CALL_DETAILS_H
+#include "pending-diagnostic.h"
+
namespace ana {
/* Helper class for handling calls to functions with known behavior. */
return update_model (state->m_region_model, eedge, ctxt);
}
+void
+custom_edge_info::get_dot_attrs (const char *&out_style,
+ const char *&out_color) const
+{
+ out_color = "red";
+ out_style = "\"dotted\"";
+}
+
/* Base implementation of custom_edge_info::create_enode vfunc. */
exploded_node *
void
call_info::add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const
+ const exploded_edge &eedge,
+ pending_diagnostic &) const
{
class call_event : public custom_event
{
emission_path->add_event
(std::make_unique<call_event> (event_loc_info (get_call_stmt ().location,
- caller_fndecl,
- stack_depth),
- this));
+ caller_fndecl,
+ stack_depth),
+ this));
}
/* Recreate a call_details instance from this call_info. */
public:
void print (pretty_printer *pp) const override;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const override;
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const override;
const gcall &get_call_stmt () const { return m_call_stmt; }
tree get_fndecl () const { return m_fndecl; }
bool
call_string::element_t::operator== (const call_string::element_t &other) const
{
- return (m_caller == other.m_caller && m_callee == other.m_callee);
+ return (m_call_sedge == other.m_call_sedge
+ && m_called_fun == other.m_called_fun);
}
/* call_string::element_t's inequality operator. */
return !(*this == other);
}
+int
+call_string::element_t::cmp (const element_t &a, const element_t &b)
+{
+ if (int src_id_cmp = (a.m_call_sedge->m_src->m_id
+ - b.m_call_sedge->m_src->m_id))
+ return src_id_cmp;
+
+ /* We don't expect multiple call sedges with the same src. */
+ gcc_assert (a.m_call_sedge->m_dest == b.m_call_sedge->m_dest);
+
+ return a.m_called_fun->funcdef_no - b.m_called_fun->funcdef_no;
+}
+
function *
call_string::element_t::get_caller_function () const
{
- return m_caller->get_function ();
+ return m_call_sedge->m_src->get_function ();
}
-function *
-call_string::element_t::get_callee_function () const
+const supernode *
+call_string::element_t::get_call_snode_in_caller () const
{
- return m_callee->get_function ();
+ return m_call_sedge->m_src;
+}
+
+const supernode *
+call_string::element_t::get_return_snode_in_caller () const
+{
+ return m_call_sedge->m_dest;
+}
+
+const gcall &
+call_string::element_t::get_call_stmt () const
+{
+ return m_call_op->get_gcall ();
}
/* Print this to PP. */
if (i > 0)
pp_string (pp, ", ");
pp_printf (pp, "(SN: %i -> SN: %i in %s)",
- e->m_callee->m_index, e->m_caller->m_index,
- function_name (e->m_caller->m_fun));
+ e->get_call_snode_in_caller ()->m_id,
+ e->get_return_snode_in_caller ()->m_id,
+ function_name (e->get_caller_function ()));
}
pp_string (pp, "]");
}
/* Return a new json::array of the form
- [{"src_snode_idx" : int,
- "dst_snode_idx" : int,
- "funcname" : str},
+ [{"call_sedge_src" : int,
+ "call_sedge_dest" : int,
+ "called_fun" : str},
...for each element in the callstring]. */
std::unique_ptr<json::value>
for (const call_string::element_t &e : m_elements)
{
auto e_obj = std::make_unique<json::object> ();
- e_obj->set_integer ("src_snode_idx", e.m_callee->m_index);
- e_obj->set_integer ("dst_snode_idx", e.m_caller->m_index);
- e_obj->set_string ("funcname", function_name (e.m_caller->m_fun));
+ e_obj->set_integer ("call_sedge_src", e.m_call_sedge->m_src->m_id);
+ e_obj->set_integer ("call_sedge_dest", e.m_call_sedge->m_dest->m_id);
+ e_obj->set_string ("called_fun", function_name (e.m_called_fun));
arr->append (std::move (e_obj));
}
return arr;
}
-/* Get or create the call_string resulting from pushing the return
- superedge for CALL_SEDGE onto the end of this call_string. */
-
-const call_string *
-call_string::push_call (const supergraph &sg,
- const call_superedge *call_sedge) const
-{
- gcc_assert (call_sedge);
- const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
- gcc_assert (return_sedge);
- return push_call (return_sedge->m_dest, return_sedge->m_src);
-}
/* Get or create the call_string resulting from pushing the call
(caller, callee) onto the end of this call_string. */
const call_string *
-call_string::push_call (const supernode *caller,
- const supernode *callee) const
+call_string::push_call (const superedge &call_sedge,
+ const call_and_return_op &call_op,
+ function &called_fun) const
{
- call_string::element_t e (caller, callee);
+ call_string::element_t e (&call_sedge, &call_op, &called_fun);
if (const call_string **slot = m_children.get (e))
return *slot;
int result = 0;
for (const call_string::element_t &e : m_elements)
{
- if (e.get_callee_function () == fun)
- result++;
if (e.get_caller_function () == fun)
result++;
+ if (e.get_callee_function () == fun)
+ result++;
}
return result;
}
if (i >= len_b)
return -1;
- /* Otherwise, compare the node pairs. */
- const call_string::element_t a_node_pair = a[i];
- const call_string::element_t b_node_pair = b[i];
- int src_cmp
- = a_node_pair.m_callee->m_index - b_node_pair.m_callee->m_index;
- if (src_cmp)
- return src_cmp;
- int dest_cmp
- = a_node_pair.m_caller->m_index - b_node_pair.m_caller->m_index;
- if (dest_cmp)
- return dest_cmp;
+ /* Otherwise, compare the elements. */
+ if (int element_cmp = call_string::element_t::cmp (a[i], b[i]))
+ return element_cmp;
i++;
// TODO: test coverage for this
}
return cmp (*cs_a, *cs_b);
}
-/* Return the pointer to callee of the topmost call in the stack,
- or nullptr if stack is empty. */
-const supernode *
-call_string::get_callee_node () const
-{
- if(m_elements.is_empty ())
- return nullptr;
- return m_elements[m_elements.length () - 1].m_callee;
-}
-
/* Return the pointer to caller of the topmost call in the stack,
or nullptr if stack is empty. */
const supernode *
-call_string::get_caller_node () const
+call_string::get_return_node_in_caller () const
{
if(m_elements.is_empty ())
return nullptr;
- return m_elements[m_elements.length () - 1].m_caller;
+ return m_elements[m_elements.length () - 1].get_return_snode_in_caller ();
}
/* Assert that this object is sane. */
return;
#endif
- gcc_assert (m_parent || m_elements.length () == 0);
+ gcc_assert ((m_parent != nullptr)
+ ^ (m_elements.length () == 0));
- /* Each entry's "caller" should be the "callee" of the previous entry. */
call_string::element_t *e;
int i;
FOR_EACH_VEC_ELT (m_elements, i, e)
- if (i > 0)
- gcc_assert (e->get_caller_function () ==
- m_elements[i - 1].get_callee_function ());
+ {
+ gcc_assert (e->m_call_op == e->m_call_sedge->get_op ());
+ /* Each entry's "caller" should be the "callee" of the
+ previous entry. */
+ if (i > 0)
+ gcc_assert (e->get_caller_function () ==
+ m_elements[i - 1].get_callee_function ());
+ }
}
/* ctor for the root/empty call_string. */
/* Log the final element in detail. */
const element_t *e = &m_elements[m_elements.length () - 1];
pp_printf (pp, "(SN: %i -> SN: %i in %s)]",
- e->m_callee->m_index, e->m_caller->m_index,
- function_name (e->m_caller->m_fun));
+ e->m_call_sedge->m_src->m_id,
+ e->m_call_sedge->m_dest->m_id,
+ function_name (e->get_caller_function ()));
}
else
pp_string (pp, "[]");
class supergraph;
class supernode;
-class call_superedge;
-class return_superedge;
+ class call_and_return_op;
-
-/* A string of return_superedge pointers, representing a call stack
- at a program point.
+/* A string of "elements" representing a call stack at a program point.
This is used to ensure that we generate interprocedurally valid paths
i.e. that we return to the same callsite that called us.
- The class stores returning calls ( which may be represented by a
- returning superedge ). We do so because this is what we need to compare
- against.
-
Instances of call_string are consolidated by the region_model_manager,
which effectively owns them: it owns the root/empty call_string, and each
call_string instance tracks its children, lazily creating them on demand,
public:
/* A struct representing an element in the call_string.
- Each element represents a path from m_callee to m_caller which represents
- returning from function. */
+ Each element represents a "call superedge" within the caller for which
+ the op was a call_and_return_op.
+ Returning from the callee to the caller will involve creating a custom
+ exploded edge from the exit supernode in the callee to the destination
+ of the call superedge within the caller. */
struct element_t
{
- element_t (const supernode *caller, const supernode *callee)
- : m_caller (caller), m_callee (callee)
+ element_t (const superedge *call_sedge,
+ const call_and_return_op *call_op,
+ function *called_fun)
+ : m_call_sedge (call_sedge), m_call_op (call_op),
+ m_called_fun (called_fun)
{
}
bool operator== (const element_t &other) const;
bool operator!= (const element_t &other) const;
+ static int cmp (const element_t &a, const element_t &b);
+
/* Accessors */
function *get_caller_function () const;
- function *get_callee_function () const;
-
- const supernode *m_caller;
- const supernode *m_callee;
+ function *get_callee_function () const { return m_called_fun; }
+ const supernode *get_call_snode_in_caller () const;
+ const supernode *get_return_snode_in_caller () const;
+ const gcall &get_call_stmt () const;
+
+ const superedge *m_call_sedge;
+ const call_and_return_op *m_call_op;
+ function *m_called_fun;
};
void print (pretty_printer *pp) const;
bool empty_p () const { return m_elements.is_empty (); }
- const call_string *push_call (const supergraph &sg,
- const call_superedge *sedge) const;
-
- const call_string *push_call (const supernode *src,
- const supernode *dest) const;
+ const call_string *push_call (const superedge &call_sedge,
+ const call_and_return_op &call_op,
+ function &called_fun) const;
const call_string *get_parent () const { return m_parent; }
int calc_recursion_depth () const;
static int cmp_ptr_ptr (const void *, const void *);
/* Accessors */
-
- const supernode *get_callee_node () const;
- const supernode *get_caller_node () const;
+ const supernode *get_return_node_in_caller () const;
unsigned length () const { return m_elements.length (); }
element_t operator[] (unsigned idx) const
{
static inline hashval_t hash (const key_type &k)
{
inchash::hash hstate;
- hstate.add_ptr (k.m_caller);
- hstate.add_ptr (k.m_callee);
+ hstate.add_ptr (k.m_call_sedge);
return hstate.end ();
}
static inline bool equal_keys (const key_type &k1, const key_type &k2)
}
template <typename T> static inline void remove (T &entry)
{
- entry.m_key = element_t (nullptr, nullptr);
+ entry.m_key = element_t (nullptr, nullptr, nullptr);
}
static const bool empty_zero_p = true;
template <typename T> static inline bool is_empty (const T &entry)
{
- return entry.m_key.m_caller == nullptr;
+ return entry.m_key.m_call_sedge == nullptr;
}
template <typename T> static inline bool is_deleted (const T &entry)
{
- return entry.m_key.m_caller == reinterpret_cast<const supernode *> (1);
+ return entry.m_key.m_call_sedge == reinterpret_cast<const superedge *> (1);
}
template <typename T> static inline void mark_empty (T &entry)
{
- entry.m_key = element_t (nullptr, nullptr);
+ entry.m_key = element_t (nullptr, nullptr, nullptr);
entry.m_value = nullptr;
}
template <typename T> static inline void mark_deleted (T &entry)
{
- entry.m_key.m_caller = reinterpret_cast<const supernode *> (1);
+ entry.m_key.m_call_sedge = reinterpret_cast<const superedge *> (1);
}
};
tree
call_summary::get_fndecl () const
{
- return m_enode->get_point ().get_fndecl ();
+ return m_enode->get_function ()->decl;
}
label_text
{
const widening_svalue *widening_summary_sval
= as_a <const widening_svalue *> (summary_sval);
- const function_point &point = widening_summary_sval->get_point ();
+ const supernode *snode = widening_summary_sval->get_snode ();
const svalue *summary_base_sval
= widening_summary_sval->get_base_svalue ();
const svalue *caller_base_sval
region_model_manager *mgr = get_manager ();
return mgr->get_or_create_widening_svalue
(summary_iter_sval->get_type (),
- point,
+ snode,
caller_base_sval,
caller_iter_sval);
}
return "end_cfg_edge";
case event_kind::catch_:
return "catch";
- case event_kind::call_edge:
- return "call_edge";
- case event_kind::return_edge:
- return "return_edge";
+ case event_kind::call_:
+ return "call";
+ case event_kind::return_:
+ return "return";
case event_kind::start_consolidated_cfg_edges:
return "start_consolidated_cfg_edges";
case event_kind::end_consolidated_cfg_edges:
function_entry_event::function_entry_event (const program_point &dst_point,
const program_state &state)
: checker_event (event_kind::function_entry,
- event_loc_info (dst_point.get_supernode
- ()->get_start_location (),
+ event_loc_info (dst_point.get_location (),
dst_point.get_fndecl (),
dst_point.get_stack_depth ())),
m_state (state)
/* state_change_event's ctor. */
-state_change_event::state_change_event (const supernode *node,
+state_change_event::state_change_event (const event_loc_info &loc_info,
const gimple *stmt,
- int stack_depth,
const state_machine &sm,
const svalue *sval,
state_machine::state_t from,
const svalue *origin,
const program_state &dst_state,
const exploded_node *enode)
-: checker_event (event_kind::state_change,
- event_loc_info (stmt->location,
- node->m_fun->decl,
- stack_depth)),
- m_node (node), m_stmt (stmt), m_sm (sm),
+: checker_event (event_kind::state_change, loc_info),
+ m_stmt (stmt),
+ m_sm (sm),
m_sval (sval), m_from (from), m_to (to),
m_origin (origin),
m_dst_state (dst_state),
#undef PROPERTY_PREFIX
}
-/* Get the callgraph_superedge for this superedge_event, which must be
- for an interprocedural edge, rather than a CFG edge. */
-
-const callgraph_superedge&
-superedge_event::get_callgraph_superedge () const
-{
- gcc_assert (m_sedge->m_kind != SUPEREDGE_CFG_EDGE);
- return *m_sedge->dyn_cast_callgraph_superedge ();
-}
-
/* Determine if this event should be filtered at the given verbosity
level. */
bool
superedge_event::should_filter_p (int verbosity) const
{
- switch (m_sedge->m_kind)
+ if (m_sedge->get_any_cfg_edge ())
{
- case SUPEREDGE_CFG_EDGE:
- {
- if (verbosity < 2)
- return true;
-
- if (verbosity < 4)
- {
- /* Filter events with empty descriptions. This ought to filter
- FALLTHRU, but retain true/false/switch edges. */
- auto pp = global_dc->clone_printer ();
- print_desc (*pp.get ());
- if (pp_formatted_text (pp.get ()) [0] == '\0')
- return true;
- }
- }
- break;
+ if (verbosity < 2)
+ return true;
- default:
- break;
+ if (verbosity < 4)
+ {
+ /* Filter events with empty descriptions. This ought to filter
+ FALLTHRU, but retain true/false/switch edges. */
+ auto pp = global_dc->clone_printer ();
+ print_desc (*pp.get ());
+ if (pp_formatted_text (pp.get ()) [0] == '\0')
+ return true;
+ }
}
return false;
}
return &m_eedge.m_dest->get_state ();
}
+const call_and_return_op *
+superedge_event::get_call_and_return_op () const
+{
+ if (m_sedge)
+ if (auto base_op = m_sedge->get_op ())
+ return base_op->dyn_cast_call_and_return_op ();
+ return nullptr;
+}
+
/* superedge_event's ctor. */
superedge_event::superedge_event (enum event_kind kind,
const exploded_edge &eedge,
const event_loc_info &loc_info)
: checker_event (kind, loc_info),
- m_eedge (eedge), m_sedge (eedge.m_sedge),
- m_var (NULL_TREE), m_critical_state (0)
+ m_eedge (eedge), m_sedge (eedge.m_sedge)
{
- /* Note that m_sedge can be nullptr for e.g. jumps through
- function pointers. */
+ gcc_assert (m_sedge);
}
/* class cfg_edge_event : public superedge_event. */
-/* Get the cfg_superedge for this cfg_edge_event. */
-
-const cfg_superedge &
-cfg_edge_event::get_cfg_superedge () const
-{
- return *m_sedge->dyn_cast_cfg_superedge ();
-}
-
/* cfg_edge_event's ctor. */
cfg_edge_event::cfg_edge_event (enum event_kind kind,
const exploded_edge &eedge,
- const event_loc_info &loc_info)
-: superedge_event (kind, eedge, loc_info)
+ const event_loc_info &loc_info,
+ const control_flow_op *op)
+: superedge_event (kind, eedge, loc_info),
+ m_op (op)
{
- gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
}
/* Implementation of diagnostics::paths::event::get_meaning vfunc for
diagnostics::paths::event::meaning
cfg_edge_event::get_meaning () const
{
- const cfg_superedge& cfg_sedge = get_cfg_superedge ();
- if (cfg_sedge.true_value_p ())
- return meaning (verb::branch, property::true_);
- else if (cfg_sedge.false_value_p ())
- return meaning (verb::branch, property::false_);
- else
- return meaning ();
+ if (::edge e = get_cfg_edge ())
+ {
+ if (e->flags & EDGE_TRUE_VALUE)
+ return meaning (verb::branch, property::true_);
+ else if (e->flags & EDGE_FALSE_VALUE)
+ return meaning (verb::branch, property::false_);
+ }
+ return meaning ();
+}
+
+::edge
+cfg_edge_event::get_cfg_edge () const
+{
+ return m_sedge->get_any_cfg_edge ();
}
/* class start_cfg_edge_event : public cfg_edge_event. */
label_text edge_desc (m_sedge->get_description (user_facing));
if (user_facing)
{
- if (edge_desc.get () && strlen (edge_desc.get ()) > 0)
+ if (edge_desc.get ()
+ && strlen (edge_desc.get ()) > 0
+ && m_op)
{
- label_text cond_desc = maybe_describe_condition (pp_show_color (&pp));
+ label_text cond_desc
+ = m_op->maybe_describe_condition (pp_show_color (&pp));
label_text result;
if (cond_desc.get ())
pp_printf (&pp,
return pp_printf (&pp,
"taking %qs edge SN:%i -> SN:%i",
edge_desc.get (),
- m_sedge->m_src->m_index,
- m_sedge->m_dest->m_index);
+ m_sedge->m_src->m_id,
+ m_sedge->m_dest->m_id);
else
return pp_printf (&pp,
"taking edge SN:%i -> SN:%i",
- m_sedge->m_src->m_index,
- m_sedge->m_dest->m_index);
+ m_sedge->m_src->m_id,
+ m_sedge->m_dest->m_id);
}
}
-/* Attempt to generate a description of any condition that holds at this edge.
-
- The intent is to make the user-facing messages more clear, especially for
- cases where there's a single or double-negative, such as
- when describing the false branch of an inverted condition.
-
- For example, rather than printing just:
-
- | if (!ptr)
- | ~
- | |
- | (1) following 'false' branch...
-
- it's clearer to spell out the condition that holds:
-
- | if (!ptr)
- | ~
- | |
- | (1) following 'false' branch (when 'ptr' is non-NULL)...
- ^^^^^^^^^^^^^^^^^^^^^^
-
- In the above example, this function would generate the highlighted
- string: "when 'ptr' is non-NULL".
-
- If the edge is not a condition, or it's not clear that a description of
- the condition would be helpful to the user, return NULL. */
-
-label_text
-start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const
-{
- const cfg_superedge& cfg_sedge = get_cfg_superedge ();
-
- if (cfg_sedge.true_value_p () || cfg_sedge.false_value_p ())
- {
- const gimple *last_stmt = m_sedge->m_src->get_last_stmt ();
- if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
- {
- enum tree_code op = gimple_cond_code (cond_stmt);
- tree lhs = gimple_cond_lhs (cond_stmt);
- tree rhs = gimple_cond_rhs (cond_stmt);
- if (cfg_sedge.false_value_p ())
- op = invert_tree_comparison (op, false /* honor_nans */);
- return maybe_describe_condition (can_colorize,
- lhs, op, rhs);
- }
- }
- return label_text::borrow (nullptr);
-}
-
-/* Subroutine of maybe_describe_condition above.
-
- Attempt to generate a user-facing description of the condition
- LHS OP RHS, but only if it is likely to make it easier for the
- user to understand a condition. */
-
-label_text
-start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
- tree lhs,
- enum tree_code op,
- tree rhs)
-{
- /* In theory we could just build a tree via
- fold_build2 (op, boolean_type_node, lhs, rhs)
- and print it with %qE on it, but this leads to warts such as
- parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'. */
-
- /* Special-case: describe testing the result of strcmp, as figuring
- out what the "true" or "false" path is can be confusing to the user. */
- if (TREE_CODE (lhs) == SSA_NAME
- && zerop (rhs))
- {
- if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs)))
- if (is_special_named_call_p (*call, "strcmp", 2))
- {
- if (op == EQ_EXPR)
- return label_text::borrow ("when the strings are equal");
- if (op == NE_EXPR)
- return label_text::borrow ("when the strings are non-equal");
- }
- }
-
- /* Only attempt to generate text for sufficiently simple expressions. */
- if (!should_print_expr_p (lhs))
- return label_text::borrow (nullptr);
- if (!should_print_expr_p (rhs))
- return label_text::borrow (nullptr);
-
- /* Special cases for pointer comparisons against NULL. */
- if (POINTER_TYPE_P (TREE_TYPE (lhs))
- && POINTER_TYPE_P (TREE_TYPE (rhs))
- && zerop (rhs))
- {
- if (op == EQ_EXPR)
- return make_label_text (can_colorize, "when %qE is NULL",
- lhs);
- if (op == NE_EXPR)
- return make_label_text (can_colorize, "when %qE is non-NULL",
- lhs);
- }
-
- return make_label_text (can_colorize, "when %<%E %s %E%>",
- lhs, op_symbol_code (op), rhs);
-}
-
-/* Subroutine of maybe_describe_condition.
-
- Return true if EXPR is we will get suitable user-facing output
- from %E on it. */
-
-bool
-start_cfg_edge_event::should_print_expr_p (tree expr)
-{
- if (TREE_CODE (expr) == SSA_NAME)
- {
- if (SSA_NAME_VAR (expr))
- return should_print_expr_p (SSA_NAME_VAR (expr));
- else
- return false;
- }
-
- if (DECL_P (expr))
- return true;
-
- if (CONSTANT_CLASS_P (expr))
- return true;
-
- return false;
-}
-
/* class catch_cfg_edge_event : public cfg_edge_event. */
diagnostics::paths::event::meaning
call_event::call_event (const exploded_edge &eedge,
const event_loc_info &loc_info)
-: superedge_event (event_kind::call_edge, eedge, loc_info)
+: superedge_event (event_kind::call_, eedge, loc_info)
{
- if (eedge.m_sedge)
- gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL);
-
m_src_snode = eedge.m_src->get_supernode ();
m_dest_snode = eedge.m_dest->get_supernode ();
}
void
call_event::print_desc (pretty_printer &pp) const
{
- if (m_critical_state && m_pending_diagnostic)
+ if (m_critical_state.m_state && m_pending_diagnostic)
{
- gcc_assert (m_var);
- tree var = fixup_tree_for_diagnostic (m_var);
+ gcc_assert (m_critical_state.m_var);
+ tree var = fixup_tree_for_diagnostic (m_critical_state.m_var);
evdesc::call_with_state evd (m_src_snode->m_fun->decl,
m_dest_snode->m_fun->decl,
var,
- m_critical_state);
+ m_critical_state.m_state);
if (m_pending_diagnostic->describe_call_with_state (pp, evd))
return;
}
return &m_eedge.m_src->get_state ();
}
-/* class return_event : public superedge_event. */
+/* class return_event : public checker_event. */
/* return_event's ctor. */
return_event::return_event (const exploded_edge &eedge,
const event_loc_info &loc_info)
-: superedge_event (event_kind::return_edge, eedge, loc_info)
+: checker_event (event_kind::return_, loc_info),
+ m_eedge (eedge)
{
- if (eedge.m_sedge)
- gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN);
-
m_src_snode = eedge.m_src->get_supernode ();
m_dest_snode = eedge.m_dest->get_supernode ();
+ m_call_and_return_op
+ = eedge.m_src->get_point ().get_call_string ().get_top_of_stack ().m_call_op;
}
/* Implementation of diagnostics::paths::event::print_desc vfunc for
state involved in the pending diagnostic, give the pending
diagnostic a chance to describe this return (in terms of
itself). */
- if (m_critical_state && m_pending_diagnostic)
+ if (m_critical_state.m_state && m_pending_diagnostic)
{
evdesc::return_of_state evd (m_dest_snode->m_fun->decl,
m_src_snode->m_fun->decl,
- m_critical_state);
+ m_critical_state.m_state);
if (m_pending_diagnostic->describe_return_of_state (pp, evd))
return;
}
start_cfg_edge,
end_cfg_edge,
catch_,
- call_edge,
- return_edge,
+ call_,
+ return_,
start_consolidated_cfg_edges,
end_consolidated_cfg_edges,
inlined_call,
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)
+ call_event (event_kind::call_)
+ return_event (event_kind::return_)
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)
class state_change_event : public checker_event
{
public:
- state_change_event (const supernode *node, const gimple *stmt,
- int stack_depth,
+ state_change_event (const event_loc_info &loc_info,
+ const gimple *stmt,
const state_machine &sm,
const svalue *sval,
state_machine::state_t from,
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;
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;
+ virtual const call_and_return_op *
+ get_call_and_return_op () const;
+
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
public:
meaning get_meaning () const override;
- const cfg_superedge& get_cfg_superedge () const;
+ ::edge get_cfg_edge () const;
protected:
- cfg_edge_event (enum event_kind kind, const exploded_edge &eedge,
- const event_loc_info &loc_info);
+ cfg_edge_event (enum event_kind kind,
+ const exploded_edge &eedge,
+ const event_loc_info &loc_info,
+ const control_flow_op *op);
+
+ const control_flow_op *m_op;
};
/* A concrete event subclass for the start of a CFG edge
{
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)
+ const event_loc_info &loc_info,
+ const control_flow_op *op)
+ : cfg_edge_event (event_kind::start_cfg_edge, eedge, loc_info, op)
{
}
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);
};
{
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)
+ const event_loc_info &loc_info,
+ const control_flow_op *op)
+ : cfg_edge_event (event_kind::end_cfg_edge, eedge, loc_info, op)
{
}
public:
catch_cfg_edge_event (const exploded_edge &eedge,
const event_loc_info &loc_info,
+ const control_flow_op &op,
tree type)
- : cfg_edge_event (event_kind::catch_, eedge, loc_info),
+ : cfg_edge_event (event_kind::catch_, eedge, loc_info, &op),
m_type (type)
{
}
tree m_type;
};
+struct critical_state
+{
+ critical_state ()
+ : m_var (NULL_TREE),
+ m_state (nullptr)
+ {
+ }
+ critical_state (tree var, state_machine::state_t state)
+ : m_var (var),
+ m_state (state)
+ {
+ }
+
+ tree m_var;
+ state_machine::state_t m_state;
+};
+
/* A concrete event subclass for an interprocedural call. */
class call_event : public superedge_event
const program_state *
get_program_state () const final 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_critical_state = critical_state (var, state);
+ }
+
protected:
tree get_caller_fndecl () const;
tree get_callee_fndecl () const;
const supernode *m_src_snode;
const supernode *m_dest_snode;
+ critical_state m_critical_state;
};
/* A concrete event subclass for an interprocedural return. */
-class return_event : public superedge_event
+class return_event : public checker_event
{
public:
return_event (const exploded_edge &eedge,
bool is_return_p () const final override;
+ const call_and_return_op *
+ get_call_and_return_op () const
+ {
+ return m_call_and_return_op;
+ }
+
+ /* 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_critical_state = critical_state (var, state);
+ }
+
+ const exploded_edge &m_eedge;
const supernode *m_src_snode;
const supernode *m_dest_snode;
+ const call_and_return_op *m_call_and_return_op;
+ critical_state m_critical_state;
};
/* A concrete event subclass for the start of a consolidated run of CFG
#include "config.h"
#define INCLUDE_MAP
+#define INCLUDE_SET
#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
class supergraph;
class supernode;
class superedge;
- class cfg_superedge;
- class switch_cfg_superedge;
- class eh_dispatch_cfg_superedge;
- class eh_dispatch_try_cfg_superedge;
- class eh_dispatch_allowed_cfg_superedge;
- class callgraph_superedge;
- class call_superedge;
- class return_superedge;
class svalue;
class region_svalue;
class checker_path;
class extrinsic_state;
class sm_state_map;
-class stmt_finder;
class program_point;
-class function_point;
class program_state;
class exploded_graph;
class exploded_node;
class exploded_edge;
class feasibility_problem;
+class feasibility_state;
class exploded_cluster;
class exploded_path;
class analysis_plan;
class call_summary_replay;
struct per_function_data;
struct interesting_t;
+class uncertainty_t;
class feasible_node;
extern tree fixup_tree_for_diagnostic (tree);
extern tree get_diagnostic_tree_for_gassign (const gassign *);
+inline bool
+useful_location_p (location_t loc)
+{
+ return get_pure_location (loc) != UNKNOWN_LOCATION;
+}
+
/* A tree, extended with stack frame information for locals, so that
we can distinguish between different values of locals within a potentially
recursive callstack. */
public:
virtual ~known_function () {}
virtual bool matches_call_types_p (const call_details &cd) const = 0;
+
+ /* A hook for performing additional checks on the expected state
+ at a call. */
+ virtual void
+ check_any_preconditions (const call_details &) const
+ {
+ // no-op
+ }
+
virtual void impl_call_pre (const call_details &) const
{
return;
/* Hook for making .dot label more readable. */
virtual void print (pretty_printer *pp) const = 0;
+ virtual void
+ get_dot_attrs (const char *&out_style,
+ const char *&out_color) const;
+
/* Hook for updating STATE when handling bifurcation. */
virtual bool update_state (program_state *state,
const exploded_edge *eedge,
region_model_context *ctxt) const = 0;
virtual void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const = 0;
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const = 0;
virtual exploded_node *create_enode (exploded_graph &eg,
const program_point &point,
extern bool is_placement_new_p (const gcall &call);
extern bool is_cxa_throw_p (const gcall &call);
extern bool is_cxa_rethrow_p (const gcall &call);
+extern bool is_cxa_end_catch_p (const gcall &call);
extern const char *get_user_facing_name (const gcall &call);
return inst;
}
-/* Get the bounded_ranges instance for EDGE of SWITCH_STMT,
- creating it if necessary, and caching it by edge. */
-
-const bounded_ranges *
-bounded_ranges_manager::
-get_or_create_ranges_for_switch (const switch_cfg_superedge *edge,
- const gswitch *switch_stmt)
-{
- /* Look in per-edge cache. */
- if (const bounded_ranges ** slot = m_edge_cache.get (edge))
- return *slot;
-
- /* Not yet in cache. */
- const bounded_ranges *all_cases_ranges
- = create_ranges_for_switch (*edge, switch_stmt);
- m_edge_cache.put (edge, all_cases_ranges);
- return all_cases_ranges;
-}
-
-/* Get the bounded_ranges instance for EDGE of SWITCH_STMT,
- creating it if necessary, for edges for which the per-edge
- cache has not yet been populated. */
-
-const bounded_ranges *
-bounded_ranges_manager::
-create_ranges_for_switch (const switch_cfg_superedge &edge,
- const gswitch *switch_stmt)
-{
- /* Get the ranges for each case label. */
- auto_vec <const bounded_ranges *> case_ranges_vec
- (gimple_switch_num_labels (switch_stmt));
-
- for (tree case_label : edge.get_case_labels ())
- {
- /* Get the ranges for this case label. */
- const bounded_ranges *case_ranges
- = make_case_label_ranges (switch_stmt, case_label);
- case_ranges_vec.quick_push (case_ranges);
- }
-
- /* Combine all the ranges for each case label into a single collection
- of ranges. */
- const bounded_ranges *all_cases_ranges
- = get_or_create_union (case_ranges_vec);
- return all_cases_ranges;
-}
-
/* Get the bounded_ranges instance for CASE_LABEL within
SWITCH_STMT. */
namespace ana {
-/* An object to own and consolidate bounded_ranges instances.
- This also caches the mapping from switch_cfg_superedge
- bounded_ranges instances, so that get_or_create_ranges_for_switch is
- memoized. */
+/* An object to own and consolidate bounded_ranges instances. */
class bounded_ranges_manager
{
public:
~bounded_ranges_manager ();
- const bounded_ranges *
- get_or_create_ranges_for_switch (const switch_cfg_superedge *edge,
- const gswitch *switch_stmt);
-
const bounded_ranges *get_or_create_empty ();
const bounded_ranges *get_or_create_point (const_tree value);
const bounded_ranges *get_or_create_range (const_tree lower_bound,
void log_stats (logger *logger, bool show_objs) const;
-private:
- const bounded_ranges *
- create_ranges_for_switch (const switch_cfg_superedge &edge,
- const gswitch *switch_stmt);
-
const bounded_ranges *
make_case_label_ranges (const gswitch *switch_stmt,
tree case_label);
+private:
const bounded_ranges *consolidate (bounded_ranges *);
struct hash_traits_t : public typed_noop_remove<bounded_ranges *>
};
typedef hash_map<bounded_ranges *, bounded_ranges *, traits_t> map_t;
map_t m_map;
-
- typedef hash_map<const switch_cfg_superedge *,
- const bounded_ranges *> edge_cache_t;
- edge_cache_t m_edge_cache;
};
/* An equivalence class within a constraint manager: a set of
class feasible_worklist;
+// struct pending_location
+
+pending_location::pending_location ()
+: m_enode (nullptr),
+ m_event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0)
+{
+}
+
+pending_location::pending_location (exploded_node *enode)
+: m_enode (enode),
+ m_event_loc_info (enode)
+{
+}
+
+pending_location::pending_location (exploded_node *enode,
+ location_t loc)
+: m_enode (enode),
+ m_event_loc_info (enode)
+{
+ m_event_loc_info.m_loc = loc;
+}
+
+std::unique_ptr<json::object>
+pending_location::to_json () const
+{
+ auto ploc_obj = std::make_unique<json::object> ();
+
+ if (m_enode)
+ ploc_obj->set_integer ("enode", m_enode->m_index);
+ // TODO: also store m_event_loc_info
+ return ploc_obj;
+}
+
/* State for finding the shortest feasible exploded_path for a
saved_diagnostic.
This is shared between all diagnostics, so that we avoid repeating work. */
std::unique_ptr<exploded_path>
get_best_epath (const exploded_node *target_enode,
- const gimple *target_stmt,
const pending_diagnostic &pd,
const char *desc, unsigned diag_idx,
std::unique_ptr<feasibility_problem> *out_problem);
std::unique_ptr<exploded_path>
explore_feasible_paths (const exploded_node *target_enode,
- const gimple *target_stmt,
const pending_diagnostic &pd,
const char *desc, unsigned diag_idx);
bool
const trimmed_graph &tg,
feasible_graph *fg,
const exploded_node *target_enode,
- const gimple *target_stmt,
const pending_diagnostic &pd,
unsigned diag_idx,
std::unique_ptr<exploded_path> *out_best_path) const;
/* class epath_finder. */
-/* Get the "best" exploded_path for reaching ENODE from the origin,
+/* Get the "best" exploded_path for reaching TARGET_ENODE from the origin,
returning ownership of it to the caller.
- If TARGET_STMT is non-NULL, then check for reaching that stmt
- within ENODE.
-
Ideally we want to report the shortest feasible path.
Return nullptr if we could not find a feasible path
(when flag_analyzer_feasibility is true).
Write any feasibility_problem to *OUT_PROBLEM. */
std::unique_ptr<exploded_path>
-epath_finder::get_best_epath (const exploded_node *enode,
- const gimple *target_stmt,
+epath_finder::get_best_epath (const exploded_node *target_enode,
const pending_diagnostic &pd,
const char *desc, unsigned diag_idx,
std::unique_ptr<feasibility_problem> *out_problem)
logger *logger = get_logger ();
LOG_SCOPE (logger);
- unsigned snode_idx = enode->get_supernode ()->m_index;
+ unsigned snode_id = target_enode->get_supernode ()->m_id;
if (logger)
logger->log ("considering %qs at EN: %i, SN: %i (sd: %i)",
- desc, enode->m_index, snode_idx, diag_idx);
+ desc, target_enode->m_index, snode_id, diag_idx);
/* State-merging means that not every path in the egraph corresponds
to a feasible one w.r.t. states.
if (logger)
logger->log ("trying to find shortest feasible path");
if (std::unique_ptr<exploded_path> epath
- = explore_feasible_paths (enode, target_stmt, pd, desc, diag_idx))
+ = explore_feasible_paths (target_enode, pd, desc, diag_idx))
{
if (logger)
logger->log ("accepting %qs at EN: %i, SN: %i (sd: %i)"
" with feasible path (length: %i)",
- desc, enode->m_index, snode_idx, diag_idx,
+ desc, target_enode->m_index, snode_id, diag_idx,
epath->length ());
return epath;
}
if (logger)
logger->log ("rejecting %qs at EN: %i, SN: %i (sd: %i)"
" due to not finding feasible path",
- desc, enode->m_index, snode_idx, diag_idx);
+ desc, target_enode->m_index, snode_id, diag_idx);
return nullptr;
}
}
if (logger)
logger->log ("trying to find shortest path ignoring feasibility");
gcc_assert (m_sep);
- std::unique_ptr<exploded_path> epath
- = std::make_unique<exploded_path> (m_sep->get_shortest_path (enode));
+ auto epath
+ = std::make_unique<exploded_path>
+ (m_sep->get_shortest_path (target_enode));
if (epath->feasible_p (logger, out_problem, m_eg.get_engine (), &m_eg))
{
if (logger)
logger->log ("accepting %qs at EN: %i, SN: %i (sn: %i)"
" with feasible path (length: %i)",
- desc, enode->m_index, snode_idx, diag_idx,
+ desc, target_enode->m_index, snode_id, diag_idx,
epath->length ());
}
else
if (logger)
logger->log ("accepting %qs at EN: %i, SN: %i (sn: %i) (length: %i)"
" despite infeasible path (due to %qs)",
- desc, enode->m_index, snode_idx, diag_idx,
+ desc, target_enode->m_index, snode_id, diag_idx,
epath->length (),
"-fno-analyzer-feasibility");
}
TARGET_ENODE by iteratively building a feasible_graph, in which
every path to a feasible_node is feasible by construction.
- If TARGET_STMT is non-NULL, then check for reaching that stmt
- within TARGET_ENODE.
-
We effectively explore the tree of feasible paths in order of shortest
path until we either find a feasible path to TARGET_ENODE, or hit
a limit and give up.
std::unique_ptr<exploded_path>
epath_finder::explore_feasible_paths (const exploded_node *target_enode,
- const gimple *target_stmt,
const pending_diagnostic &pd,
const char *desc, unsigned diag_idx)
{
{
auto_checking_feasibility sentinel (mgr);
- while (process_worklist_item (&worklist, tg, &fg, target_enode, target_stmt,
+ while (process_worklist_item (&worklist, tg, &fg, target_enode,
pd, diag_idx, &best_path))
{
/* Empty; the work is done within process_worklist_item. */
const trimmed_graph &tg,
feasible_graph *fg,
const exploded_node *target_enode,
- const gimple *target_stmt,
const pending_diagnostic &pd,
unsigned diag_idx,
std::unique_ptr<exploded_path> *out_best_path) const
" (length: %i)",
target_enode->m_index, diag_idx,
succ_fnode->get_path_length ());
- if (!pd.check_valid_fpath_p (*succ_fnode, target_stmt))
+ if (!pd.check_valid_fpath_p (*succ_fnode))
{
if (logger)
logger->log ("rejecting feasible path due to"
/* saved_diagnostic's ctor. */
saved_diagnostic::saved_diagnostic (const state_machine *sm,
- const pending_location &ploc,
+ pending_location &&ploc,
tree var,
const svalue *sval,
state_machine::state_t state,
std::unique_ptr<pending_diagnostic> d,
unsigned idx)
-: m_sm (sm), m_enode (ploc.m_enode), m_snode (ploc.m_snode),
- m_stmt (ploc.m_stmt),
- /* stmt_finder could be on-stack; we want our own copy that can
- outlive that. */
- m_stmt_finder (ploc.m_finder ? ploc.m_finder->clone () : nullptr),
- m_loc (ploc.m_loc),
+: m_sm (sm),
+ m_ploc (std::move (ploc)),
m_var (var), m_sval (sval), m_state (state),
m_d (std::move (d)), m_trailing_eedge (nullptr),
m_idx (idx),
{
/* We must have an enode in order to be able to look for paths
through the exploded_graph to this diagnostic. */
- gcc_assert (m_enode);
+ gcc_assert (m_ploc.m_enode);
+}
+
+const supernode *
+saved_diagnostic::get_supernode () const
+{
+ return m_ploc.m_enode->get_supernode ();
}
bool
return (m_sm == other.m_sm
/* We don't compare m_enode. */
- && m_snode == other.m_snode
- && m_stmt == other.m_stmt
- /* We don't compare m_stmt_finder. */
- && m_loc == other.m_loc
+ && get_supernode () == other.get_supernode ()
+ && (m_ploc.m_event_loc_info.m_loc
+ == other.m_ploc.m_event_loc_info.m_loc)
&& pending_diagnostic::same_tree_p (m_var, other.m_var)
&& m_state == other.m_state
&& m_d->equal_p (*other.m_d)
/* Return a new json::object of the form
{"sm": optional str,
- "enode": int,
- "snode": int,
+ "ploc": {},
"sval": optional str,
"state": optional str,
"path_length": optional int,
if (m_sm)
sd_obj->set_string ("sm", m_sm->get_name ());
- sd_obj->set_integer ("enode", m_enode->m_index);
- sd_obj->set_integer ("snode", m_snode->m_index);
+ sd_obj->set ("ploc", m_ploc.to_json ());
if (m_sval)
sd_obj->set ("sval", m_sval->to_json ());
if (m_state)
sd_obj->set_string ("pending_diagnostic", m_d->get_kind ());
sd_obj->set_integer ("idx", m_idx);
- /* We're not yet JSONifying the following fields:
- const gimple *m_stmt;
- stmt_finder *m_stmt_finder;
- tree m_var;
- exploded_edge *m_trailing_eedge;
- enum status m_status;
- feasibility_problem *m_problem;
- auto_delete_vec <pending_note> m_notes;
- */
-
return sd_obj;
}
}
pp_newline (pp);
}
- if (m_stmt)
- {
- pp_string (pp, "stmt: ");
- pp_gimple_stmt_1 (pp, m_stmt, 0, (dump_flags_t)0);
- pp_newline (pp);
- }
if (m_var)
pp_printf (pp, "var: %qE\n", m_var);
if (m_sval)
}
}
-/* Use PF to find the best exploded_path for this saved_diagnostic,
+/* Use PF to find the best exploded_path for this saved_diagnostic, if any,
and store it in m_best_epath.
- If we don't have a specific location in m_loc and m_stmt is still nullptr,
- use m_stmt_finder on the epath to populate m_stmt.
Return true if a best path was found. */
bool
LOG_SCOPE (logger);
m_problem = nullptr;
- m_best_epath = pf->get_best_epath (m_enode, m_stmt,
+ m_best_epath = pf->get_best_epath (m_ploc.m_enode,
*m_d, m_d->get_kind (), m_idx,
&m_problem);
return false;
gcc_assert (m_best_epath);
- if (m_loc == UNKNOWN_LOCATION)
- {
- if (m_stmt == nullptr)
- {
- gcc_assert (m_stmt_finder);
- m_stmt = m_stmt_finder->find_stmt (*m_best_epath);
- }
- gcc_assert (m_stmt);
- }
return true;
}
bool
saved_diagnostic::supercedes_p (const saved_diagnostic &other) const
{
- /* They should be at the same stmt. */
- if (m_stmt != other.m_stmt)
+ if (get_supernode () != other.get_supernode ())
return false;
+
/* return early if OTHER won't be superseded anyway. */
if (!m_d->supercedes_p (*other.m_d))
return false;
#define PROPERTY_PREFIX "gcc/analyzer/saved_diagnostic/"
if (m_sm)
props.set_string (PROPERTY_PREFIX "sm", m_sm->get_name ());
- props.set_integer (PROPERTY_PREFIX "enode", m_enode->m_index);
- props.set_integer (PROPERTY_PREFIX "snode", m_snode->m_index);
- if (m_stmt)
- {
- pretty_printer pp;
- pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0);
- props.set_string (PROPERTY_PREFIX "stmt", pp_formatted_text (&pp));
- }
+ props.set (PROPERTY_PREFIX "ploc", m_ploc.to_json ());
if (m_var)
props.set (PROPERTY_PREFIX "var", tree_to_json (m_var));
if (m_sval)
const feasibility_problem *m_feasibility_problem;
};
-/* Determine the emission location for PD at STMT in FUN. */
-
-static location_t
-get_emission_location (const gimple *stmt, function *fun,
- const pending_diagnostic &pd)
-{
- location_t loc = get_stmt_location (stmt, fun);
-
- /* Allow the pending_diagnostic to fix up the location. */
- loc = pd.fixup_location (loc, true);
-
- return loc;
-}
-
/* class diagnostic_manager. */
/* diagnostic_manager's ctor. */
bool
diagnostic_manager::add_diagnostic (const state_machine *sm,
- const pending_location &ploc,
+ pending_location &&ploc,
tree var,
const svalue *sval,
state_machine::state_t state,
gcc_assert (ploc.m_enode);
/* If this warning is ultimately going to be rejected by a -Wno-analyzer-*
- flag, reject it now.
- We can only do this for diagnostics where we already know the stmt,
- and thus can determine the emission location. */
- if (ploc.m_stmt)
- {
- location_t loc
- = get_emission_location (ploc.m_stmt, ploc.m_snode->m_fun, *d);
- int option = d->get_controlling_option ();
- if (!warning_enabled_at (loc, option))
- {
- if (get_logger ())
- get_logger ()->log ("rejecting disabled warning %qs",
- d->get_kind ());
- m_num_disabled_diagnostics++;
- return false;
- }
- }
+ flag, reject it now. */
+ {
+ location_t loc = ploc.m_event_loc_info.m_loc;
+ loc = d->fixup_location (loc, true);
+ int option = d->get_controlling_option ();
+ if (!warning_enabled_at (loc, option))
+ {
+ if (get_logger ())
+ get_logger ()->log ("rejecting disabled warning %qs",
+ d->get_kind ());
+ m_num_disabled_diagnostics++;
+ return false;
+ }
+ }
saved_diagnostic *sd
- = new saved_diagnostic (sm, ploc, var, sval, state, std::move (d),
+ = new saved_diagnostic (sm,
+ std::move (ploc),
+ var,
+ sval,
+ state,
+ std::move (d),
m_saved_diagnostics.length ());
m_saved_diagnostics.safe_push (sd);
- ploc.m_enode->add_diagnostic (sd);
+ sd->m_ploc.m_enode->add_diagnostic (sd);
if (get_logger ())
log ("adding saved diagnostic %i at SN %i to EN %i: %qs",
sd->get_index (),
- ploc.m_snode->m_index, ploc.m_enode->m_index, sd->m_d->get_kind ());
+ sd->get_supernode ()->m_id,
+ sd->m_ploc.m_enode->m_index,
+ sd->m_d->get_kind ());
return true;
}
Take ownership of D (or delete it). */
bool
-diagnostic_manager::add_diagnostic (const pending_location &ploc,
+diagnostic_manager::add_diagnostic (pending_location &&ploc,
std::unique_ptr<pending_diagnostic> d)
{
gcc_assert (ploc.m_enode);
- return add_diagnostic (nullptr, ploc, NULL_TREE, nullptr, 0, std::move (d));
+ return add_diagnostic (nullptr, std::move (ploc),
+ NULL_TREE, nullptr, 0, std::move (d));
}
/* Add PN to the most recent saved_diagnostic. */
{
public:
dedupe_key (const saved_diagnostic &sd)
- : m_sd (sd), m_stmt (sd.m_stmt), m_loc (sd.m_loc)
+ : m_sd (sd),
+ m_loc (sd.m_ploc.m_event_loc_info.m_loc)
{
- gcc_assert (m_stmt || m_loc != UNKNOWN_LOCATION);
}
hashval_t hash () const
{
inchash::hash hstate;
- hstate.add_ptr (m_stmt);
// TODO: m_sd
return hstate.end ();
}
bool operator== (const dedupe_key &other) const
{
return (m_sd == other.m_sd
- && m_stmt == other.m_stmt
&& m_loc == other.m_loc);
}
- location_t get_location () const
- {
- if (m_loc != UNKNOWN_LOCATION)
- return m_loc;
- gcc_assert (m_stmt);
- return m_stmt->location;
- }
+ location_t get_location () const { return m_loc; }
/* A qsort comparator for use by dedupe_winners::emit_best
to sort them into location_t order. */
}
const saved_diagnostic &m_sd;
- const gimple *m_stmt;
location_t m_loc;
};
if (!sd->calc_best_epath (pf))
return;
+ const exploded_path *epath = sd->get_best_epath ();
+ gcc_assert (epath);
+
+ /* Now we have an exploded path, use it for pending_locations that are
+ affected by such things, and for deduplication. */
+ if (sd->m_ploc.m_fixer_for_epath)
+ sd->m_ploc.m_fixer_for_epath->fixup_for_epath (*epath, sd->m_ploc);
+
dedupe_key *key = new dedupe_key (*sd);
if (saved_diagnostic **slot = m_map.get (key))
{
saved_diagnostic *sd;
FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
log ("[%i] sd: %qs at EN: %i, SN: %i",
- i, sd->m_d->get_kind (), sd->m_enode->m_index,
- sd->m_snode->m_index);
+ i, sd->m_d->get_kind (), sd->m_ploc.m_enode->m_index,
+ sd->get_supernode ()->m_id);
}
if (m_saved_diagnostics.length () == 0)
{
LOG_SCOPE (get_logger ());
log ("sd[%i]: %qs at SN: %i",
- sd.get_index (), sd.m_d->get_kind (), sd.m_snode->m_index);
+ sd.get_index (), sd.m_d->get_kind (), sd.get_supernode ()->m_id);
log ("num dupes: %i", sd.get_num_dupes ());
const exploded_path *epath = sd.get_best_epath ();
generated. These don't get pruned, as they are probably pertinent. */
sd.add_any_saved_events (emission_path);
- /* Add a final event to the path, covering the diagnostic itself.
- We use the final enode from the epath, which might be different from
- the sd.m_enode, as the dedupe code doesn't care about enodes, just
- snodes. */
+ /* Add a final event to the path, covering the diagnostic itself. */
{
const exploded_node *const enode = epath->get_final_enode ();
- const gimple *stmt = sd.m_stmt;
- event_loc_info loc_info (get_stmt_location (stmt, enode->get_function ()),
- enode->get_function ()->decl,
- enode->get_stack_depth ());
- if (sd.m_stmt_finder)
- sd.m_stmt_finder->update_event_loc_info (loc_info);
- sd.m_d->add_final_event (sd.m_sm, enode, loc_info,
+ sd.m_d->add_final_event (sd.m_sm, enode, sd.m_ploc.m_event_loc_info,
sd.m_var, sd.m_state, &emission_path);
}
emission_path.prepare_for_emission (sd.m_d.get ());
- location_t loc = sd.m_loc;
- if (loc == UNKNOWN_LOCATION)
- loc = get_emission_location (sd.m_stmt, sd.m_snode->m_fun, *sd.m_d);
+ location_t loc = sd.m_ploc.m_event_loc_info.m_loc;
+ loc = sd.m_d->fixup_location (loc, true);
/* Allow the pending_diagnostic to fix up the locations of events. */
emission_path.fixup_locations (sd.m_d.get ());
rich_loc.set_path (&emission_path);
auto_diagnostic_group d;
- auto_cfun sentinel (sd.m_snode->m_fun);
+ auto_cfun sentinel (sd.get_supernode ()->m_fun);
pending_diagnostic_metadata m (sd);
diagnostic_emission_context diag_ctxt (sd, rich_loc, m, get_logger ());
if (sd.m_d->emit (diag_ctxt))
const region *base_reg = reg->get_base_region ();
if (tree decl = base_reg->maybe_get_decl ())
if (DECL_P (decl)
- && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION)
+ && useful_location_p (DECL_SOURCE_LOCATION (decl)))
{
emission_path->add_region_creation_events
(pb.get_pending_diagnostic (),
if (&sm != m_pb.get_sm ())
return false;
const exploded_node *src_node = m_eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
const exploded_node *dst_node = m_eedge.m_dest;
- const gimple *stmt = src_point.get_stmt ();
- const supernode *supernode = src_point.get_supernode ();
+ const gimple *stmt = m_eedge.maybe_get_stmt ();
const program_state &dst_state = dst_node->get_state ();
- int stack_depth = src_stack_depth;
-
m_emission_path->add_event
- (std::make_unique<state_change_event> (supernode,
+ (std::make_unique<state_change_event> (m_eedge.m_src,
stmt,
- stack_depth,
sm,
nullptr,
src_sm_val,
{
if (&sm != m_pb.get_sm ())
return false;
+
const exploded_node *src_node = m_eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
const exploded_node *dst_node = m_eedge.m_dest;
- const gimple *stmt = src_point.get_stmt ();
- const supernode *supernode = src_point.get_supernode ();
+ const gimple *stmt = m_eedge.maybe_get_stmt ();
const program_state &dst_state = dst_node->get_state ();
- int stack_depth = src_stack_depth;
-
- if (m_eedge.m_sedge
- && m_eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE)
- {
- supernode = src_point.get_supernode ();
- stmt = supernode->get_last_stmt ();
- stack_depth = src_stack_depth;
- }
-
- /* Bulletproofing for state changes at calls/returns;
- TODO: is there a better way? */
- if (!stmt)
- return false;
-
m_emission_path->add_event
- (std::make_unique<state_change_event> (supernode,
+ (std::make_unique<state_change_event> (m_eedge.m_src,
stmt,
- stack_depth,
sm,
sval,
src_sm_val,
return false;
}
-/* An sm_context for adding state_change_event on assignments to NULL,
- where the default state isn't m_start. Storing such state in the
- sm_state_map would lead to bloat of the exploded_graph, so we want
- to leave it as a default state, and inject state change events here
- when we have a diagnostic.
- Find transitions of constants, for handling on_zero_assignment. */
-
-struct null_assignment_sm_context : public sm_context
-{
- null_assignment_sm_context (int sm_idx,
- const state_machine &sm,
- const program_state *old_state,
- const program_state *new_state,
- const gimple *stmt,
- const program_point *point,
- checker_path *emission_path,
- const extrinsic_state &ext_state)
- : sm_context (sm_idx, sm), m_old_state (old_state), m_new_state (new_state),
- m_stmt (stmt), m_point (point), m_emission_path (emission_path),
- m_ext_state (ext_state)
- {
- }
-
- tree get_fndecl_for_call (const gcall &/*call*/) final override
- {
- return NULL_TREE;
- }
-
- state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
- tree var) final override
- {
- const svalue *var_old_sval
- = m_old_state->m_region_model->get_rvalue (var, nullptr);
- const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
-
- state_machine::state_t current
- = old_smap->get_state (var_old_sval, m_ext_state);
-
- return current;
- }
-
- state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
- const svalue *sval) final override
- {
- const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
- state_machine::state_t current = old_smap->get_state (sval, m_ext_state);
- return current;
- }
-
- void set_next_state (const gimple *stmt,
- tree var,
- state_machine::state_t to,
- tree origin ATTRIBUTE_UNUSED) final override
- {
- state_machine::state_t from = get_state (stmt, var);
- if (from != m_sm.get_start_state ())
- return;
- if (!is_transition_to_null (to))
- return;
-
- const svalue *var_new_sval
- = m_new_state->m_region_model->get_rvalue (var, nullptr);
-
- const supernode *supernode = m_point->get_supernode ();
- int stack_depth = m_point->get_stack_depth ();
-
- m_emission_path->add_event
- (std::make_unique<state_change_event> (supernode,
- m_stmt,
- stack_depth,
- m_sm,
- var_new_sval,
- from, to,
- nullptr,
- *m_new_state,
- nullptr));
- }
-
- void set_next_state (const gimple *stmt,
- const svalue *sval,
- state_machine::state_t to,
- tree origin ATTRIBUTE_UNUSED) final override
- {
- state_machine::state_t from = get_state (stmt, sval);
- if (from != m_sm.get_start_state ())
- return;
- if (!is_transition_to_null (to))
- return;
-
- const supernode *supernode = m_point->get_supernode ();
- int stack_depth = m_point->get_stack_depth ();
-
- m_emission_path->add_event
- (std::make_unique<state_change_event> (supernode,
- m_stmt,
- stack_depth,
- m_sm,
- sval,
- from, to,
- nullptr,
- *m_new_state,
- nullptr));
- }
-
- void warn (const supernode *, const gimple *,
- tree, std::unique_ptr<pending_diagnostic>) final override
- {
- }
- void warn (const supernode *, const gimple *,
- const svalue *, std::unique_ptr<pending_diagnostic>) final override
- {
- }
-
- tree get_diagnostic_tree (tree expr) final override
- {
- return expr;
- }
-
- tree get_diagnostic_tree (const svalue *sval) final override
- {
- return m_new_state->m_region_model->get_representative_tree (sval);
- }
-
- state_machine::state_t get_global_state () const final override
- {
- return 0;
- }
-
- void set_global_state (state_machine::state_t) final override
- {
- /* No-op. */
- }
-
- void clear_all_per_svalue_state () final override
- {
- /* No-op. */
- }
-
- void on_custom_transition (custom_transition *) final override
- {
- }
-
- tree is_zero_assignment (const gimple *stmt) final override
- {
- const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
- if (!assign_stmt)
- return NULL_TREE;
- if (const svalue *sval
- = m_new_state->m_region_model->get_gassign_result (assign_stmt, nullptr))
- if (tree cst = sval->maybe_get_constant ())
- if (::zerop(cst))
- return gimple_assign_lhs (assign_stmt);
- return NULL_TREE;
- }
-
- const program_state *get_old_program_state () const final override
- {
- return m_old_state;
- }
- const program_state *get_new_program_state () const final override
- {
- return m_new_state;
- }
-
- /* We only care about transitions to the "null" state
- within sm-malloc. Special-case this. */
- static bool is_transition_to_null (state_machine::state_t s)
- {
- return !strcmp (s->get_name (), "null");
- }
-
- const program_state *m_old_state;
- const program_state *m_new_state;
- const gimple *m_stmt;
- const program_point *m_point;
- checker_path *m_emission_path;
- const extrinsic_state &m_ext_state;
-};
-
/* Subroutine of diagnostic_manager::build_emission_path.
Add any events for EEDGE to EMISSION_PATH. */
for_each_state_change (src_state, dst_state, pb.get_ext_state (),
&visitor);
+ /* Give diagnostics an opportunity to inject extra events, or
+ to override the rest of this function. */
+ pending_diagnostic *pd = pb.get_pending_diagnostic ();
+ if (pd->maybe_add_custom_events_for_eedge (eedge, emission_path))
+ return;
+
/* Allow non-standard edges to add events, e.g. when rewinding from
longjmp to a setjmp. */
if (eedge.m_custom_info)
- eedge.m_custom_info->add_events_to_path (emission_path, eedge);
+ eedge.m_custom_info->add_events_to_path (emission_path, eedge, *pd);
- /* Add events for superedges, function entries, and for statements. */
- switch (dst_point.get_kind ())
+ /* Don't add events for insignificant edges at verbosity levels below 3. */
+ if (m_verbosity < 3)
+ if (!significant_edge_p (pb, eedge))
+ return;
+
+ /* Add events for operations. */
+ if (eedge.m_sedge)
+ if (auto op = eedge.m_sedge->get_op ())
+ op->add_any_events_for_eedge (eedge, *emission_path);
+
+ /* Add events for function entry. */
+ if (dst_point.get_supernode ()->entry_p ())
{
- default:
- break;
- case PK_BEFORE_SUPERNODE:
- if (src_point.get_kind () == PK_AFTER_SUPERNODE)
- {
- if (eedge.m_sedge)
- add_events_for_superedge (pb, eedge, emission_path);
- }
- /* Add function entry events. */
- if (dst_point.get_supernode ()->entry_p ())
+ pb.get_pending_diagnostic ()->add_function_entry_event
+ (eedge, emission_path);
+ /* Create region_creation_events for on-stack regions within
+ this frame. */
+ if (interest)
{
- pb.get_pending_diagnostic ()->add_function_entry_event
- (eedge, emission_path);
- /* Create region_creation_events for on-stack regions within
- this frame. */
- if (interest)
- {
- unsigned i;
- const region *reg;
- FOR_EACH_VEC_ELT (interest->m_region_creation, i, reg)
- if (const frame_region *frame = reg->maybe_get_frame_region ())
- if (frame->get_fndecl () == dst_point.get_fndecl ())
- {
- const region *base_reg = reg->get_base_region ();
- if (tree decl = base_reg->maybe_get_decl ())
- if (DECL_P (decl)
- && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION)
- {
- emission_path->add_region_creation_events
- (pb.get_pending_diagnostic (),
- reg, dst_state.m_region_model,
- event_loc_info (DECL_SOURCE_LOCATION (decl),
- dst_point.get_fndecl (),
- dst_stack_depth),
- m_verbosity > 3);
- }
- }
- }
- }
- break;
- case PK_BEFORE_STMT:
- {
- const gimple *stmt = dst_point.get_stmt ();
- const gcall *call = dyn_cast <const gcall *> (stmt);
- if (call && is_setjmp_call_p (*call))
- emission_path->add_event
- (std::make_unique<setjmp_event>
- (event_loc_info (stmt->location,
- dst_point.get_fndecl (),
- dst_stack_depth),
- dst_node,
- *call));
- else
- emission_path->add_event
- (std::make_unique<statement_event> (stmt,
- dst_point.get_fndecl (),
- dst_stack_depth, dst_state));
-
- /* Create state change events for assignment to NULL.
- Iterate through the stmts in dst_enode, adding state change
- events for them. */
- if (dst_state.m_region_model)
- {
- log_scope s (get_logger (), "processing run of stmts");
- program_state iter_state (dst_state);
- program_point iter_point (dst_point);
- while (1)
- {
- const gimple *stmt = iter_point.get_stmt ();
- if (const gassign *assign = dyn_cast<const gassign *> (stmt))
- {
- const extrinsic_state &ext_state = pb.get_ext_state ();
- program_state old_state (iter_state);
- iter_state.m_region_model->on_assignment (assign, nullptr);
- for (unsigned i = 0; i < ext_state.get_num_checkers (); i++)
+ unsigned i;
+ const region *reg;
+ FOR_EACH_VEC_ELT (interest->m_region_creation, i, reg)
+ if (const frame_region *frame = reg->maybe_get_frame_region ())
+ if (frame->get_fndecl () == dst_point.get_fndecl ())
+ {
+ const region *base_reg = reg->get_base_region ();
+ if (tree decl = base_reg->maybe_get_decl ())
+ if (DECL_P (decl)
+ && useful_location_p (DECL_SOURCE_LOCATION (decl)))
{
- const state_machine &sm = ext_state.get_sm (i);
- null_assignment_sm_context sm_ctxt (i, sm,
- &old_state,
- &iter_state,
- stmt,
- &iter_point,
- emission_path,
- pb.get_ext_state ());
- sm.on_stmt (sm_ctxt, dst_point.get_supernode (), stmt);
- // TODO: what about phi nodes?
+ emission_path->add_region_creation_events
+ (pb.get_pending_diagnostic (),
+ reg, dst_state.m_region_model,
+ event_loc_info (DECL_SOURCE_LOCATION (decl),
+ dst_point.get_fndecl (),
+ dst_stack_depth),
+ m_verbosity > 3);
}
- }
- iter_point.next_stmt ();
- if (iter_point.get_kind () == PK_AFTER_SUPERNODE
- || (dst_node->m_succs.length () > 1
- && (iter_point
- == dst_node->m_succs[0]->m_dest->get_point ())))
- break;
- }
-
- }
- }
- break;
+ }
+ }
}
/* Look for changes in dynamic extents, which will identify
return true;
}
-/* Subroutine of diagnostic_manager::add_events_for_eedge
- where EEDGE has an underlying superedge i.e. a CFG edge,
- or an interprocedural call/return.
- Add any events for the superedge to EMISSION_PATH. */
-
-void
-diagnostic_manager::add_events_for_superedge (const path_builder &pb,
- const exploded_edge &eedge,
- checker_path *emission_path)
- const
-{
- gcc_assert (eedge.m_sedge);
-
- /* Give diagnostics an opportunity to override this function. */
- pending_diagnostic *pd = pb.get_pending_diagnostic ();
- if (pd->maybe_add_custom_events_for_superedge (eedge, emission_path))
- return;
-
- /* Don't add events for insignificant edges at verbosity levels below 3. */
- if (m_verbosity < 3)
- if (!significant_edge_p (pb, eedge))
- return;
-
- const exploded_node *src_node = eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const exploded_node *dst_node = eedge.m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
- const int dst_stack_depth = dst_point.get_stack_depth ();
- const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
-
- switch (eedge.m_sedge->m_kind)
- {
- case SUPEREDGE_CFG_EDGE:
- {
- if (auto eh_dispatch_try_sedge
- = eedge.m_sedge->dyn_cast_eh_dispatch_try_cfg_superedge ())
- {
- if (eh_dispatch_try_sedge->get_eh_catch ())
- {
- const region_model *model = src_node->get_state ().m_region_model;
- auto curr_thrown_exception_node
- = model->get_current_thrown_exception ();
- gcc_assert (curr_thrown_exception_node);
- tree type = curr_thrown_exception_node->maybe_get_type ();
- emission_path->add_event
- (std::make_unique<catch_cfg_edge_event>
- (eedge,
- event_loc_info (dst_point.get_supernode ()->get_start_location (),
- dst_point.get_fndecl (),
- dst_stack_depth),
- type));
- return;
- }
- else
- {
- /* We have the "uncaught exception" sedge, from eh_dispatch
- to a block containing resx.
- Don't add any events for this, so that we can consolidate
- adjacent stack unwinding events. */
- return;
- }
- }
-
- emission_path->add_event
- (std::make_unique<start_cfg_edge_event>
- (eedge,
- event_loc_info
- (last_stmt ? last_stmt->location : UNKNOWN_LOCATION,
- src_point.get_fndecl (),
- src_stack_depth)));
- emission_path->add_event
- (std::make_unique<end_cfg_edge_event>
- (eedge,
- event_loc_info (dst_point.get_supernode ()->get_start_location (),
- dst_point.get_fndecl (),
- dst_stack_depth)));
- }
- break;
-
- case SUPEREDGE_CALL:
- pd->add_call_event (eedge, emission_path);
- break;
-
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- {
- /* TODO: add a subclass for this, or generate events for the
- summary. */
- emission_path->add_event
- (std::make_unique<debug_event>
- (event_loc_info (last_stmt
- ? last_stmt->location
- : UNKNOWN_LOCATION,
- src_point.get_fndecl (),
- src_stack_depth),
- "call summary"));
- }
- break;
-
- case SUPEREDGE_RETURN:
- {
- const return_superedge *return_edge
- = as_a <const return_superedge *> (eedge.m_sedge);
-
- const gcall &call_stmt = return_edge->get_call_stmt ();
- emission_path->add_event
- (std::make_unique<return_event>
- (eedge,
- event_loc_info (call_stmt.location,
- dst_point.get_fndecl (),
- dst_stack_depth)));
- }
- break;
- }
-}
-
/* Prune PATH, based on the verbosity level, to the most pertinent
events for a diagnostic that involves VAR ending in state STATE
(for state machine SM).
/* Don't filter these. */
break;
- case event_kind::call_edge:
+ case event_kind::call_:
{
call_event *event = (call_event *)base_event;
const region_model *callee_model
callsite_expr expr;
tree caller_var;
- if(event->m_sedge)
- {
- const callgraph_superedge& cg_superedge
- = event->get_callgraph_superedge ();
- if (cg_superedge.m_cedge)
- caller_var
- = cg_superedge.map_expr_from_callee_to_caller (callee_var,
- &expr);
- else
- caller_var = caller_model->get_representative_tree (sval);
- }
- else
+ if (auto op = event->get_call_and_return_op ())
+ {
+ tree callee_fndecl
+ = event->m_eedge.m_dest->get_point ().get_fndecl ();
+ caller_var
+ = op->map_expr_from_callee_to_caller (callee_fndecl,
+ callee_var,
+ &expr);
+ }
+ else
caller_var = caller_model->get_representative_tree (sval);
if (caller_var)
}
break;
- case event_kind::return_edge:
+ case event_kind::return_:
{
if (sval)
{
= event->m_eedge.m_src->get_state ().m_region_model;
callsite_expr expr;
- tree callee_var;
- if (event->m_sedge)
- {
- const callgraph_superedge& cg_superedge
- = event->get_callgraph_superedge ();
- if (cg_superedge.m_cedge)
- callee_var
- = cg_superedge.map_expr_from_caller_to_callee (caller_var,
- &expr);
- else
- callee_var = callee_model->get_representative_tree (sval);
- }
- else
- callee_var = callee_model->get_representative_tree (sval);
+ tree callee_var;
+
+ if (auto op = event->get_call_and_return_op ())
+ {
+ tree callee_fndecl
+ = event->m_eedge.m_src->get_point ().get_fndecl ();
+ callee_var
+ = op->map_expr_from_caller_to_callee (callee_fndecl,
+ caller_var,
+ &expr);
+ }
+ else
+ callee_var = callee_model->get_representative_tree (sval);
if (callee_var)
{
gcc_assert (old_start_ev->get_kind () == event_kind::start_cfg_edge);
const start_cfg_edge_event *old_start_cfg_ev
= (const start_cfg_edge_event *)old_start_ev;
- const cfg_superedge& first_cfg_sedge
- = old_start_cfg_ev->get_cfg_superedge ();
bool edge_sense;
- if (first_cfg_sedge.true_value_p ())
- edge_sense = true;
- else if (first_cfg_sedge.false_value_p ())
- edge_sense = false;
- else
- continue;
+ if (::edge e = old_start_cfg_ev->get_cfg_edge ())
+ {
+ if (e->flags & EDGE_TRUE_VALUE)
+ edge_sense = true;
+ else if (e->flags & EDGE_FALSE_VALUE)
+ edge_sense = false;
+ else
+ continue;
+ }
/* Find a run of CFG start/end event pairs from
[start_idx, next_idx)
gcc_assert (iter_ev->get_kind () == event_kind::start_cfg_edge);
const start_cfg_edge_event *iter_cfg_ev
= (const start_cfg_edge_event *)iter_ev;
- const cfg_superedge& iter_cfg_sedge
- = iter_cfg_ev->get_cfg_superedge ();
+ ::edge e = iter_cfg_ev->get_cfg_edge ();
+ if (!e)
+ break;
if (edge_sense)
{
- if (!iter_cfg_sedge.true_value_p ())
+ if (!(e->flags & EDGE_TRUE_VALUE))
break;
}
else
{
- if (!iter_cfg_sedge.false_value_p ())
+ if (!(e->flags & EDGE_FALSE_VALUE))
break;
}
next_idx += 2;
#ifndef GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
#define GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
+#include "analyzer/supergraph.h"
+#include "analyzer/event-loc-info.h"
+
namespace ana {
class epath_finder;
+/* A bundle of information capturing where a pending_diagnostic should
+ be emitted. */
+
+struct pending_location
+{
+public:
+ class fixer_for_epath
+ {
+ public:
+ virtual ~fixer_for_epath () = default;
+
+ virtual void
+ fixup_for_epath (const exploded_path &epath,
+ pending_location &ploc) const = 0;
+ };
+
+ pending_location ();
+ pending_location (exploded_node *enode);
+ pending_location (exploded_node *enode,
+ location_t loc);
+
+ location_t get_location () const
+ {
+ return m_event_loc_info.m_loc;
+ }
+
+ std::unique_ptr<json::object> to_json () const;
+
+ /* The enode with which a diagnostic is to be associated,
+ for tracking feasibility. */
+ exploded_node *m_enode;
+
+ /* Information to use for the location of the warning,
+ and for the location of the "final warning" in the path. */
+ event_loc_info m_event_loc_info;
+
+ /* Optional hook for use when eventually emitting the diagnostic
+ for fixing up m_event_loc_info based on the specific epath. */
+ std::unique_ptr<fixer_for_epath> m_fixer_for_epath;
+};
+
+extern std::unique_ptr<pending_location::fixer_for_epath>
+make_ploc_fixer_for_epath_for_leak_diagnostic (const exploded_graph &eg,
+ tree var);
+
/* A to-be-emitted diagnostic stored within diagnostic_manager. */
class saved_diagnostic
{
public:
saved_diagnostic (const state_machine *sm,
- const pending_location &ploc,
+ pending_location &&ploc,
tree var, const svalue *sval,
state_machine::state_t state,
std::unique_ptr<pending_diagnostic> d,
void
maybe_add_sarif_properties (diagnostics::sarif_object &result_obj) const;
+ const supernode *get_supernode () const;
+
//private:
const state_machine *m_sm;
- const exploded_node *m_enode;
- const supernode *m_snode;
- const gimple *m_stmt;
- std::unique_ptr<stmt_finder> m_stmt_finder;
- location_t m_loc;
+ pending_location m_ploc;
tree m_var;
const svalue *m_sval;
state_machine::state_t m_state;
class path_builder;
-/* A bundle of information capturing where a pending_diagnostic should
- be emitted. */
-
-struct pending_location
-{
-public:
- pending_location (exploded_node *enode,
- const supernode *snode,
- const gimple *stmt,
- const stmt_finder *finder)
- : m_enode (enode),
- m_snode (snode),
- m_stmt (stmt),
- m_finder (finder),
- m_loc (UNKNOWN_LOCATION)
- {
- gcc_assert (m_stmt || m_finder);
- }
-
- /* ctor for cases where we have a location_t but there isn't any
- gimple stmt associated with the diagnostic. */
-
- pending_location (exploded_node *enode,
- const supernode *snode,
- location_t loc)
- : m_enode (enode),
- m_snode (snode),
- m_stmt (nullptr),
- m_finder (nullptr),
- m_loc (loc)
- {
- }
-
- exploded_node *m_enode;
- const supernode *m_snode;
- const gimple *m_stmt;
- const stmt_finder *m_finder;
- location_t m_loc;
-};
-
/* A class with responsibility for saving pending diagnostics, so that
they can be emitted after the exploded_graph is complete.
This lets us de-duplicate diagnostics, and find the shortest path
std::unique_ptr<json::object> to_json () const;
bool add_diagnostic (const state_machine *sm,
- const pending_location &ploc,
+ pending_location &&ploc,
tree var,
const svalue *sval,
state_machine::state_t state,
std::unique_ptr<pending_diagnostic> d);
- bool add_diagnostic (const pending_location &ploc,
+ bool add_diagnostic (pending_location &&ploc,
std::unique_ptr<pending_diagnostic> d);
void add_note (std::unique_ptr<pending_note> pn);
bool significant_edge_p (const path_builder &pb,
const exploded_edge &eedge) const;
- void add_events_for_superedge (const path_builder &pb,
- const exploded_edge &eedge,
- checker_path *emission_path) const;
-
void prune_path (checker_path *path,
const state_machine *sm,
const svalue *sval,
#include "stringpool.h"
#include "attribs.h"
#include "tree-dfa.h"
+#include "gimple-predict.h"
#include "text-art/dump.h"
#include "analyzer/call-info.h"
#include "analyzer/known-function-manager.h"
#include "analyzer/call-summary.h"
+#include "analyzer/impl-sm-context.h"
/* For an overview, see gcc/doc/analyzer.texi. */
uncertainty_t *uncertainty,
path_context *path_ctxt,
const gimple *stmt,
- stmt_finder *stmt_finder,
bool *out_could_have_done_work)
: m_eg (&eg), m_logger (eg.get_logger ()),
m_enode_for_diag (enode_for_diag),
m_old_state (old_state),
m_new_state (new_state),
m_stmt (stmt),
- m_stmt_finder (stmt_finder),
m_ext_state (eg.get_ext_state ()),
m_uncertainty (uncertainty),
m_path_ctxt (path_ctxt),
m_old_state (nullptr),
m_new_state (state),
m_stmt (nullptr),
- m_stmt_finder (nullptr),
m_ext_state (ext_state),
m_uncertainty (uncertainty),
m_path_ctxt (nullptr),
}
bool
-impl_region_model_context::warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder)
+impl_region_model_context::warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&ploc)
{
LOG_FUNC (get_logger ());
- auto curr_stmt_finder = custom_finder ? custom_finder : m_stmt_finder;
- if (m_stmt == nullptr && curr_stmt_finder == nullptr)
- {
- if (get_logger ())
- get_logger ()->log ("rejecting diagnostic: no stmt");
- return false;
- }
if (m_eg)
{
bool terminate_path = d->terminate_path_p ();
- pending_location ploc (m_enode_for_diag,
- m_enode_for_diag->get_supernode (),
- m_stmt,
- curr_stmt_finder);
- if (m_eg->get_diagnostic_manager ().add_diagnostic (ploc,
+ if (m_eg->get_diagnostic_manager ().add_diagnostic (std::move (ploc),
std::move (d)))
{
if (m_path_ctxt
return m_setjmp_record.m_enode->m_index;
}
-/* Concrete implementation of sm_context, wiring it up to the rest of this
- file. */
-
-class impl_sm_context : public sm_context
-{
-public:
- impl_sm_context (exploded_graph &eg,
- int sm_idx,
- const state_machine &sm,
- exploded_node *enode_for_diag,
- const program_state *old_state,
- program_state *new_state,
- const sm_state_map *old_smap,
- sm_state_map *new_smap,
- path_context *path_ctxt,
- const stmt_finder *stmt_finder = nullptr,
- bool unknown_side_effects = false)
- : sm_context (sm_idx, sm),
- m_logger (eg.get_logger ()),
- m_eg (eg), m_enode_for_diag (enode_for_diag),
- m_old_state (old_state), m_new_state (new_state),
- m_old_smap (old_smap), m_new_smap (new_smap),
- m_path_ctxt (path_ctxt),
- m_stmt_finder (stmt_finder),
- m_unknown_side_effects (unknown_side_effects)
- {
- }
-
- logger *get_logger () const { return m_logger.get_logger (); }
-
- tree get_fndecl_for_call (const gcall &call) final override
- {
- impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
- nullptr, &call);
- region_model *model = m_new_state->m_region_model;
- return model->get_fndecl_for_call (call, &old_ctxt);
- }
-
- state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
- tree var) final override
- {
- logger * const logger = get_logger ();
- LOG_FUNC (logger);
- /* Use nullptr ctxt on this get_rvalue call to avoid triggering
- uninitialized value warnings. */
- const svalue *var_old_sval
- = m_old_state->m_region_model->get_rvalue (var, nullptr);
-
- state_machine::state_t current
- = m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
- return current;
- }
- state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
- const svalue *sval) final override
- {
- logger * const logger = get_logger ();
- LOG_FUNC (logger);
- state_machine::state_t current
- = m_old_smap->get_state (sval, m_eg.get_ext_state ());
- return current;
- }
-
-
- void set_next_state (const gimple *,
- tree var,
- state_machine::state_t to,
- tree origin) final override
- {
- logger * const logger = get_logger ();
- LOG_FUNC (logger);
- const svalue *var_new_sval
- = m_new_state->m_region_model->get_rvalue (var, nullptr);
- const svalue *origin_new_sval
- = m_new_state->m_region_model->get_rvalue (origin, nullptr);
-
- /* We use the new sval here to avoid issues with uninitialized values. */
- state_machine::state_t current
- = m_old_smap->get_state (var_new_sval, m_eg.get_ext_state ());
- if (logger)
- logger->log ("%s: state transition of %qE: %s -> %s",
- m_sm.get_name (),
- var,
- current->get_name (),
- to->get_name ());
- m_new_smap->set_state (m_new_state->m_region_model, var_new_sval,
- to, origin_new_sval, m_eg.get_ext_state ());
- }
-
- void set_next_state (const gimple *stmt,
- const svalue *sval,
- state_machine::state_t to,
- tree origin) final override
- {
- logger * const logger = get_logger ();
- LOG_FUNC (logger);
- impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
- nullptr, stmt);
-
- const svalue *origin_new_sval
- = m_new_state->m_region_model->get_rvalue (origin, nullptr);
-
- state_machine::state_t current
- = m_old_smap->get_state (sval, m_eg.get_ext_state ());
- if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("%s: state transition of ",
- m_sm.get_name ());
- sval->dump_to_pp (logger->get_printer (), true);
- logger->log_partial (": %s -> %s",
- current->get_name (),
- to->get_name ());
- logger->end_log_line ();
- }
- m_new_smap->set_state (m_new_state->m_region_model, sval,
- to, origin_new_sval, m_eg.get_ext_state ());
- }
-
- void warn (const supernode *snode, const gimple *stmt,
- tree var,
- std::unique_ptr<pending_diagnostic> d) final override
- {
- LOG_FUNC (get_logger ());
- gcc_assert (d);
- const svalue *var_old_sval
- = m_old_state->m_region_model->get_rvalue (var, nullptr);
- state_machine::state_t current
- = (var
- ? m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ())
- : m_old_smap->get_global_state ());
- bool terminate_path = d->terminate_path_p ();
- pending_location ploc (m_enode_for_diag, snode, stmt, m_stmt_finder);
- m_eg.get_diagnostic_manager ().add_diagnostic
- (&m_sm, ploc,
- var, var_old_sval, current, std::move (d));
- if (m_path_ctxt
- && terminate_path
- && flag_analyzer_suppress_followups)
- m_path_ctxt->terminate_path ();
- }
-
- void warn (const supernode *snode, const gimple *stmt,
- const svalue *sval,
- std::unique_ptr<pending_diagnostic> d) final override
- {
- LOG_FUNC (get_logger ());
- gcc_assert (d);
- state_machine::state_t current
- = (sval
- ? m_old_smap->get_state (sval, m_eg.get_ext_state ())
- : m_old_smap->get_global_state ());
- bool terminate_path = d->terminate_path_p ();
- pending_location ploc (m_enode_for_diag, snode, stmt, m_stmt_finder);
- m_eg.get_diagnostic_manager ().add_diagnostic
- (&m_sm, ploc,
- NULL_TREE, sval, current, std::move (d));
- if (m_path_ctxt
- && terminate_path
- && flag_analyzer_suppress_followups)
- m_path_ctxt->terminate_path ();
- }
-
- /* Hook for picking more readable trees for SSA names of temporaries,
- so that rather than e.g.
- "double-free of '<unknown>'"
- we can print:
- "double-free of 'inbuf.data'". */
-
- tree get_diagnostic_tree (tree expr) final override
- {
- /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
- likely to be the least surprising tree to report. */
- if (TREE_CODE (expr) != SSA_NAME)
- return expr;
- if (SSA_NAME_VAR (expr) != NULL)
- return expr;
-
- gcc_assert (m_new_state);
- const svalue *sval = m_new_state->m_region_model->get_rvalue (expr, nullptr);
- /* Find trees for all regions storing the value. */
- if (tree t = m_new_state->m_region_model->get_representative_tree (sval))
- return t;
- else
- return expr;
- }
-
- tree get_diagnostic_tree (const svalue *sval) final override
- {
- return m_new_state->m_region_model->get_representative_tree (sval);
- }
-
- state_machine::state_t get_global_state () const final override
- {
- return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
- }
-
- void set_global_state (state_machine::state_t state) final override
- {
- m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
- }
-
- void clear_all_per_svalue_state () final override
- {
- m_new_state->m_checker_states[m_sm_idx]->clear_all_per_svalue_state ();
- }
-
- void on_custom_transition (custom_transition *transition) final override
- {
- transition->impl_transition (&m_eg,
- const_cast<exploded_node *> (m_enode_for_diag),
- m_sm_idx);
- }
-
- tree is_zero_assignment (const gimple *stmt) final override
- {
- const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
- if (!assign_stmt)
- return NULL_TREE;
- impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, m_old_state, m_new_state, nullptr, nullptr, stmt);
- if (const svalue *sval
- = m_new_state->m_region_model->get_gassign_result (assign_stmt,
- &old_ctxt))
- if (tree cst = sval->maybe_get_constant ())
- if (::zerop(cst))
- return gimple_assign_lhs (assign_stmt);
- return NULL_TREE;
- }
-
- path_context *get_path_context () const final override
- {
- return m_path_ctxt;
- }
-
- bool unknown_side_effects_p () const final override
- {
- return m_unknown_side_effects;
- }
-
- const program_state *get_old_program_state () const final override
- {
- return m_old_state;
- }
-
- const program_state *get_new_program_state () const final override
- {
- return m_new_state;
- }
-
- log_user m_logger;
- exploded_graph &m_eg;
- exploded_node *m_enode_for_diag;
- const program_state *m_old_state;
- program_state *m_new_state;
- const sm_state_map *m_old_smap;
- sm_state_map *m_new_smap;
- path_context *m_path_ctxt;
- const stmt_finder *m_stmt_finder;
-
- /* Are we handling an external function with unknown side effects? */
- bool m_unknown_side_effects;
-};
-
bool
impl_region_model_context::
get_state_map_by_name (const char *name,
old_smap,
new_smap,
m_path_ctxt,
- m_stmt_finder,
false);
}
return true;
}
-/* Subclass of stmt_finder for finding the best stmt to report the leak at,
- given the emission path. */
+/* Subclass of pending_location::fixer_for_epath for finding the best stmt
+ to report the leak at, given the emission path. */
-class leak_stmt_finder : public stmt_finder
+class leak_ploc_fixer_for_epath : public pending_location::fixer_for_epath
{
public:
- leak_stmt_finder (const exploded_graph &eg, tree var)
- : m_eg (eg), m_var (var) {}
-
- std::unique_ptr<stmt_finder> clone () const final override
- {
- return std::make_unique<leak_stmt_finder> (m_eg, m_var);
- }
+ leak_ploc_fixer_for_epath (const exploded_graph &eg, tree var)
+ : m_eg (eg), m_var (var)
+ {}
- const gimple *find_stmt (const exploded_path &epath)
- final override
+ void
+ fixup_for_epath (const exploded_path &epath,
+ pending_location &ploc) const final override
{
logger * const logger = m_eg.get_logger ();
LOG_FUNC (logger);
+ /* Handle the interprocedural case where we leak the retval at a return
+ because the caller discards the return value. */
+ if (m_var
+ && TREE_CODE (m_var) == RESULT_DECL)
+ {
+ auto &point = ploc.m_enode->get_point ();
+ if (point.get_stack_depth () > 1)
+ if (point.get_supernode ()->exit_p ())
+ {
+ /* Get the program_point for the call within the caller. */
+ auto &cs = point.get_call_string ();
+ auto caller_snode = cs.get_return_node_in_caller ();
+ gcc_assert (caller_snode);
+ program_point caller_point (caller_snode, *cs.get_parent ());
+ ploc.m_event_loc_info = event_loc_info (caller_point);
+ return;
+ }
+ }
+
+ /* Handle the case where we have e.g.:
+ | var = malloc (N);
+ | var = NULL;
+ which with SSA becomes e.g.:
+ | var_0 = malloc (N);
+ | var_1 = nullptr;
+ and thus leads to the leak being found at the enode where "var_0" goes
+ out of scope.
+ Fix up the location of the leak to report it at the write of NULL. */
if (m_var && TREE_CODE (m_var) == SSA_NAME)
{
+ log_scope sentinel (logger, "looking for write to sibling SSA name");
/* Locate the final write to this SSA name in the path. */
const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
int idx_of_def_stmt;
- bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
- if (!found)
- goto not_found;
+ if (epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt))
+ {
+ /* What was the next write to the underlying var
+ after the SSA name was set? (if any). */
- /* What was the next write to the underlying var
- after the SSA name was set? (if any). */
+ for (unsigned idx = idx_of_def_stmt + 1;
+ idx < epath.m_edges.length ();
+ ++idx)
+ {
+ const exploded_edge *eedge = epath.m_edges[idx];
+ if (logger)
+ logger->log ("eedge[%i]: EN %i -> EN %i",
+ idx,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ const gimple *stmt = eedge->maybe_get_stmt ();
+ if (!stmt)
+ continue;
+ if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+ {
+ tree lhs = gimple_assign_lhs (assign);
+ if (TREE_CODE (lhs) == SSA_NAME
+ && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
+ {
+ if (logger)
+ logger->log ("using location 0%lx from gassign",
+ assign->location);
+ ploc.m_event_loc_info.m_loc = assign->location;
+ return;
+ }
+ }
+ }
+ }
+ }
- for (unsigned idx = idx_of_def_stmt + 1;
- idx < epath.m_edges.length ();
- ++idx)
+ /* If the epath ends at a function exit node, the location is at
+ the final "}". Try walking backward along EPATH, looking for a
+ the first suitable stmt with a better location. */
+ gcc_assert (ploc.m_enode->get_supernode ());
+ const greturn *return_stmt = nullptr;
+ if (ploc.m_enode->get_supernode ()->exit_p ()
+ && has_return_stmt_p (epath, return_stmt, logger))
+ {
+ /* If we have "return SSA_NAME;" on EPATH, keep track of the
+ pertinent SSA name as we walk backwards through EPATH. */
+ tree retval = NULL_TREE;
+ if (return_stmt)
+ retval = gimple_return_retval (return_stmt);
+
+ log_scope sentinel (logger, "walking backward along epath");
+ int idx;
+ const exploded_edge *eedge;
+ FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, idx, eedge)
{
- const exploded_edge *eedge = epath.m_edges[idx];
if (logger)
- logger->log ("eedge[%i]: EN %i -> EN %i",
- idx,
- eedge->m_src->m_index,
- eedge->m_dest->m_index);
- const exploded_node *dst_node = eedge->m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const gimple *stmt = dst_point.get_stmt ();
- if (!stmt)
- continue;
- if (const gassign *assign = dyn_cast <const gassign *> (stmt))
{
- tree lhs = gimple_assign_lhs (assign);
- if (TREE_CODE (lhs) == SSA_NAME
- && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
- return assign;
+ logger->log ("eedge[%i]: EN %i -> EN %i",
+ idx,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ if (retval)
+ logger->log (" retval: %qE", retval);
+ }
+ if (auto op = eedge->maybe_get_op ())
+ {
+ if (retval)
+ if (auto phis = op->dyn_cast_phis_for_edge_op ())
+ {
+ for (auto iter : phis->get_pairs ())
+ if (retval == iter.m_dst)
+ {
+ /* We have "PHI(RETVAL = SRC);"
+ Track SRC instead */
+ retval = iter.m_src;
+ if (logger)
+ logger->log ("updating retval to %qE", retval);
+ }
+ }
+ if (const gimple *stmt = op->maybe_get_stmt ())
+ if (consider_stmt_location_p (*stmt, retval))
+ if (useful_location_p (stmt->location))
+ {
+ if (logger)
+ logger->log ("using location 0x%lx from stmt",
+ stmt->location);
+ ploc.m_event_loc_info.m_loc = stmt->location;
+ return;
+ }
}
}
}
+ }
- not_found:
+private:
+ static bool
+ has_return_stmt_p (const exploded_path &epath,
+ const greturn *&out_greturn,
+ logger *logger)
+ {
+ LOG_SCOPE (logger);
- /* Look backwards for the first statement with a location. */
- int i;
+ int idx;
const exploded_edge *eedge;
- FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+ FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, idx, eedge)
{
- if (logger)
- logger->log ("eedge[%i]: EN %i -> EN %i",
- i,
- eedge->m_src->m_index,
- eedge->m_dest->m_index);
- const exploded_node *dst_node = eedge->m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const gimple *stmt = dst_point.get_stmt ();
- if (stmt)
- if (get_pure_location (stmt->location) != UNKNOWN_LOCATION)
- return stmt;
+ if (eedge->m_src->get_stack_depth ()
+ != eedge->m_dest->get_stack_depth ())
+ {
+ /* We have interprocedural activity, and
+ presumably are no longer in the function where
+ EPATH terminates.
+ Give up. */
+ return false;
+ }
+ if (auto op = eedge->maybe_get_op ())
+ {
+ switch (op->get_kind ())
+ {
+ default:
+ break;
+ case operation::kind::return_stmt:
+ if (logger)
+ logger->log ("found return_stmt");
+ out_greturn = &((const greturn_op *)op)->get_greturn ();
+ return true;
+ case operation::kind::predict_stmt:
+ {
+ auto &stmt = ((const gimple_stmt_op *)op)->get_stmt ();
+ switch (gimple_predict_predictor (&stmt))
+ {
+ case PRED_TREE_EARLY_RETURN:
+ /* Assume this is due to a "return;" in the user's
+ code. */
+ if (logger)
+ logger->log ("assuming a return: PRED_TREE_EARLY_RETURN");
+ return true;
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ }
}
-
- gcc_unreachable ();
- return nullptr;
+ return false;
}
- void update_event_loc_info (event_loc_info &) final override
+ /* When certain statements show up on the epath of a leak
+ at an exit node, if they have locations, these locations
+ tend to be better locations for the leak.
+ Return true for such statements (but without checking their
+ locations). */
+ static bool
+ consider_stmt_location_p (const gimple &stmt,
+ tree retval)
{
- /* No-op. */
+ if (retval && TREE_CODE (retval) == SSA_NAME)
+ if (&stmt == SSA_NAME_DEF_STMT (retval))
+ return true;
+
+ switch (stmt.code)
+ {
+ default:
+ break;
+ case GIMPLE_CALL:
+ {
+ const gcall &call = *as_a <const gcall *> (&stmt);
+ if (is_cxa_end_catch_p (call))
+ return true;
+ }
+ break;
+ case GIMPLE_PREDICT:
+ case GIMPLE_RETURN:
+ return true;
+ }
+ return false;
}
-private:
const exploded_graph &m_eg;
tree m_var;
};
+std::unique_ptr<pending_location::fixer_for_epath>
+make_ploc_fixer_for_epath_for_leak_diagnostic (const exploded_graph &eg,
+ tree var)
+{
+ return std::make_unique<leak_ploc_fixer_for_epath> (eg, var);
+}
+
/* A measurement of how good EXPR is for presenting to the user, so
that e.g. we can say prefer printing
"leak of 'tmp.m_ptr'"
const supernode *iter = snode;
while (true)
{
- if (iter->return_p ())
+ if (iter->exit_p ())
return true;
if (iter->m_succs.length () != 1)
return false;
const superedge *sedge = iter->m_succs[0];
- if (sedge->get_kind () != SUPEREDGE_CFG_EDGE)
- return false;
+
+ if (auto op = sedge->get_op ())
+ if (op->get_kind () == operation::kind::return_stmt)
+ return true;
+
iter = sedge->m_dest;
/* Impose a limit to ensure we terminate for pathological cases.
return _val;
EXIT BB.*/
- if (++count > 3)
+ if (++count > 4)
return false;
}
}
logger->log ("best leaked_tree: NULL");
}
- leak_stmt_finder stmt_finder (*m_eg, leaked_tree);
gcc_assert (m_enode_for_diag);
/* Don't complain about leaks when returning from "main". */
m_new_state);
if (pd)
{
- pending_location ploc (m_enode_for_diag,
- m_enode_for_diag->get_supernode (),
- m_stmt,
- &stmt_finder);
+ pending_location ploc (get_pending_location_for_diag ());
+ ploc.m_fixer_for_epath
+ = std::make_unique<leak_ploc_fixer_for_epath> (*m_eg, leaked_tree);
m_eg->get_diagnostic_manager ().add_diagnostic
- (&sm, ploc,
+ (&sm, std::move (ploc),
leaked_tree_for_diag, sval, state, std::move (pd));
}
}
m_old_state->m_checker_states[sm_idx],
m_new_state->m_checker_states[sm_idx],
m_path_ctxt);
- sm.on_condition (sm_ctxt,
- (m_enode_for_diag
- ? m_enode_for_diag->get_supernode ()
- : nullptr),
- m_stmt,
- lhs, op, rhs);
+ sm.on_condition (sm_ctxt, lhs, op, rhs);
}
}
m_old_state->m_checker_states[sm_idx],
m_new_state->m_checker_states[sm_idx],
m_path_ctxt);
- sm.on_bounded_ranges (sm_ctxt,
- (m_enode_for_diag
- ? m_enode_for_diag->get_supernode ()
- : nullptr),
- m_stmt, sval, ranges);
+ sm.on_bounded_ranges (sm_ctxt, sval, ranges);
}
}
m_old_state->m_checker_states[sm_idx],
m_new_state->m_checker_states[sm_idx],
m_path_ctxt);
- sm.on_phi (sm_ctxt, m_enode_for_diag->get_supernode (), phi, rhs);
+ sm.on_phi (sm_ctxt, phi, rhs);
}
}
*m_out_could_have_done_work = true;
}
+pending_location
+impl_region_model_context::get_pending_location_for_diag () const
+{
+ if (m_stmt && useful_location_p (m_stmt->location))
+ return pending_location (m_enode_for_diag, m_stmt->location);
+ else
+ return pending_location (m_enode_for_diag);
+}
+
/* struct point_and_state. */
/* Assert that this object is sane. */
default: gcc_unreachable ();
case status::worklist: return "worklist";
case status::processed: return "processed";
+ case status::special: return "special";
case status::merger: return "merger";
case status::bulk_merged: return "bulk_merged";
}
gcc_checking_assert (ps.get_state ().m_region_model->canonicalized_p ());
}
-/* Get the stmt that was processed in this enode at index IDX.
- IDX is an index within the stmts processed at this enode, rather
- than within those of the supernode. */
-
-const gimple *
-exploded_node::get_processed_stmt (unsigned idx) const
-{
- gcc_assert (idx < m_num_processed_stmts);
- const program_point &point = get_point ();
- gcc_assert (point.get_kind () == PK_BEFORE_STMT);
- const supernode *snode = get_supernode ();
- const unsigned int point_stmt_idx = point.get_stmt_idx ();
- const unsigned int idx_within_snode = point_stmt_idx + idx;
- const gimple *stmt = snode->m_stmts[idx_within_snode];
- return stmt;
-}
-
/* For use by dump_dot, get a value for the .dot "fillcolor" attribute.
Colorize by sm-state, to make it easier to see how sm-state propagates
through the exploded_graph. */
m_ps.get_point ().print (pp, f);
pp_newline (pp);
- const extrinsic_state &ext_state = args.m_eg.get_ext_state ();
- const program_state &state = m_ps.get_state ();
- state.dump_to_pp (ext_state, false, true, pp);
- pp_newline (pp);
+ bool show_state = true;
+
+ /* Don't show the state if we have a single predecessor
+ and the state hasn't changed. */
+ if (m_preds.length () == 1
+ && get_state () == m_preds[0]->m_src->get_state ())
+ show_state = false;
- dump_processed_stmts (pp);
+ if (show_state)
+ {
+ const extrinsic_state &ext_state = args.m_eg.get_ext_state ();
+ const program_state &state = m_ps.get_state ();
+ state.dump_to_pp (ext_state, false, true, pp);
+ pp_newline (pp);
+ }
}
dump_saved_diagnostics (pp);
pp_flush (pp);
}
-/* Show any stmts that were processed within this enode,
- and their index within the supernode. */
-void
-exploded_node::dump_processed_stmts (pretty_printer *pp) const
-{
- if (m_num_processed_stmts > 0)
- {
- const program_point &point = get_point ();
- gcc_assert (point.get_kind () == PK_BEFORE_STMT);
- const supernode *snode = get_supernode ();
- const unsigned int point_stmt_idx = point.get_stmt_idx ();
-
- pp_printf (pp, "stmts: %i", m_num_processed_stmts);
- pp_newline (pp);
- for (unsigned i = 0; i < m_num_processed_stmts; i++)
- {
- const unsigned int idx_within_snode = point_stmt_idx + i;
- const gimple *stmt = snode->m_stmts[idx_within_snode];
- pp_printf (pp, " %i: ", idx_within_snode);
- pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
- pp_newline (pp);
- }
- }
-}
-
/* Dump any saved_diagnostics at this enode to PP. */
void
namespace ana {
-/* Modify STATE in place, applying the effects of the stmt at this node's
- point. */
-
-exploded_node::on_stmt_flags
-exploded_node::on_stmt (exploded_graph &eg,
- const supernode *snode,
- const gimple *stmt,
- program_state *state,
- uncertainty_t *uncertainty,
- bool *out_could_have_done_work,
- path_context *path_ctxt)
-{
- logger *logger = eg.get_logger ();
- LOG_SCOPE (logger);
- if (logger)
- {
- logger->start_log_line ();
- pp_gimple_stmt_1 (logger->get_printer (), stmt, 0, (dump_flags_t)0);
- logger->end_log_line ();
- }
-
- /* Update input_location in case of ICE: make it easier to track down which
- source construct we're failing to handle. */
- input_location = stmt->location;
+/* Verify that the stack at LONGJMP_POINT is still valid, given a call
+ to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
+ called in must still be valid.
- gcc_assert (state->m_region_model);
+ Caveat: this merely checks the call_strings in the points; it doesn't
+ detect the case where a frame returns and is then called again. */
- /* Preserve the old state. It is used here for looking
- up old checker states, for determining state transitions, and
- also within impl_region_model_context and impl_sm_context for
- going from tree to svalue_id. */
- const program_state old_state (*state);
+static bool
+valid_longjmp_stack_p (const program_point &longjmp_point,
+ const program_point &setjmp_point)
+{
+ const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
+ const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
- impl_region_model_context ctxt (eg, this,
- &old_state, state, uncertainty,
- path_ctxt, stmt, nullptr,
- out_could_have_done_work);
-
- /* Handle call summaries here. */
- if (cgraph_edge *cgedge
- = supergraph_call_edge (snode->get_function (), stmt))
- if (eg.get_analysis_plan ().use_summary_p (cgedge))
- {
- function *called_fn = get_ultimate_function_for_cgraph_edge (cgedge);
- per_function_data *called_fn_data
- = eg.get_per_function_data (called_fn);
- if (called_fn_data)
- {
- gcc_assert (called_fn);
- return replay_call_summaries (eg,
- snode,
- *as_a <const gcall *> (stmt),
- state,
- path_ctxt,
- *called_fn,
- *called_fn_data,
- &ctxt);
- }
- }
+ if (cs_at_longjmp.length () < cs_at_setjmp.length ())
+ return false;
- bool unknown_side_effects = false;
- bool terminate_path = false;
+ /* Check that the call strings match, up to the depth of the
+ setjmp point. */
+ for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
+ if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
+ return false;
- on_stmt_pre (eg, stmt, state, &terminate_path,
- &unknown_side_effects, &ctxt);
+ return true;
+}
- if (terminate_path)
- return on_stmt_flags::terminate_path ();
+/* A pending_diagnostic subclass for complaining about bad longjmps,
+ where the enclosing function of the "setjmp" has returned (and thus
+ the stack frame no longer exists). */
- int sm_idx;
- sm_state_map *smap;
- FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
- {
- const state_machine &sm = eg.get_ext_state ().get_sm (sm_idx);
- const sm_state_map *old_smap
- = old_state.m_checker_states[sm_idx];
- sm_state_map *new_smap = state->m_checker_states[sm_idx];
- impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
- old_smap, new_smap, path_ctxt, nullptr,
- unknown_side_effects);
-
- /* Allow the state_machine to handle the stmt. */
- if (sm.on_stmt (sm_ctxt, snode, stmt))
- unknown_side_effects = false;
- }
+class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
+{
+public:
+ stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
+ const program_point &setjmp_point)
+ : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
+ m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
+ {}
- if (path_ctxt->terminate_path_p ())
- return on_stmt_flags::terminate_path ();
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_stale_setjmp_buffer;
+ }
- on_stmt_post (stmt, state, unknown_side_effects, &ctxt);
+ bool emit (diagnostic_emission_context &ctxt) final override
+ {
+ return ctxt.warn ("%qs called after enclosing function of %qs has returned",
+ get_user_facing_name (m_longjmp_call),
+ get_user_facing_name (m_setjmp_call));
+ }
- return on_stmt_flags ();
-}
+ const char *get_kind () const final override
+ { return "stale_jmp_buf"; }
-/* Handle the pre-sm-state part of STMT, modifying STATE in-place.
- Write true to *OUT_TERMINATE_PATH if the path should be terminated.
- Write true to *OUT_UNKNOWN_SIDE_EFFECTS if the stmt has unknown
- side effects. */
-
-void
-exploded_node::on_stmt_pre (exploded_graph &eg,
- const gimple *stmt,
- program_state *state,
- bool *out_terminate_path,
- bool *out_unknown_side_effects,
- region_model_context *ctxt)
-{
- /* Handle special-case calls that require the full program_state. */
- if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
- {
- const gcall &call = *call_stmt;
- if (is_special_named_call_p (call, "__analyzer_dump", 0))
- {
- /* Handle the builtin "__analyzer_dump" by dumping state
- to stderr. */
- state->dump (eg.get_ext_state (), true);
- return;
- }
- else if (is_special_named_call_p (call, "__analyzer_dump_sarif", 0))
- {
- state->dump_sarif (eg.get_ext_state ());
- return;
- }
- else if (is_special_named_call_p (call, "__analyzer_dump_dot", 0))
- {
- state->dump_dot (eg.get_ext_state ());
- return;
- }
- else if (is_special_named_call_p (call, "__analyzer_dump_state", 2))
- {
- state->impl_call_analyzer_dump_state (call, eg.get_ext_state (),
- ctxt);
- return;
- }
- else if (is_setjmp_call_p (call))
- {
- state->m_region_model->on_setjmp (call, this, ctxt);
- if (ctxt)
- ctxt->maybe_did_work ();
- return;
- }
- else if (is_longjmp_call_p (call))
- {
- on_longjmp (eg, call, state, ctxt);
- *out_terminate_path = true;
- if (ctxt)
- ctxt->maybe_did_work ();
- return;
- }
- else if (is_cxa_throw_p (call))
- {
- on_throw (eg, call, state, false, ctxt);
- *out_terminate_path = true;
- return;
- }
- else if (is_cxa_rethrow_p (call))
- {
- on_throw (eg, call, state, true, ctxt);
- *out_terminate_path = true;
- return;
- }
- }
- else if (const gresx *resx = dyn_cast <const gresx *> (stmt))
- {
- on_resx (eg, *resx, state, ctxt);
- *out_terminate_path = true;
- return;
- }
-
- /* Otherwise, defer to m_region_model. */
- state->m_region_model->on_stmt_pre (stmt,
- out_unknown_side_effects,
- ctxt);
-}
-
-/* Handle the post-sm-state part of STMT, modifying STATE in-place. */
-
-void
-exploded_node::on_stmt_post (const gimple *stmt,
- program_state *state,
- bool unknown_side_effects,
- region_model_context *ctxt)
-{
- if (const gcall *call = dyn_cast <const gcall *> (stmt))
- state->m_region_model->on_call_post (*call, unknown_side_effects, ctxt);
-}
-
-/* A concrete call_info subclass representing a replay of a call summary. */
-
-class call_summary_edge_info : public call_info
-{
-public:
- call_summary_edge_info (const call_details &cd,
- const function &called_fn,
- call_summary &summary,
- const extrinsic_state &ext_state)
- : call_info (cd, called_fn),
- m_called_fn (called_fn),
- m_summary (summary),
- m_ext_state (ext_state)
- {}
-
- bool update_state (program_state *state,
- const exploded_edge *,
- region_model_context *ctxt) const final override
- {
- /* Update STATE based on summary_end_state. */
- call_details cd (get_call_details (state->m_region_model, ctxt));
- call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
- const program_state &summary_end_state = m_summary.get_state ();
- return state->replay_call_summary (r, summary_end_state);
- }
-
- bool update_model (region_model *model,
- const exploded_edge *,
- region_model_context *ctxt) const final override
- {
- /* Update STATE based on summary_end_state. */
- call_details cd (get_call_details (model, ctxt));
- call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
- const program_state &summary_end_state = m_summary.get_state ();
- model->replay_call_summary (r, *summary_end_state.m_region_model);
- return true;
- }
-
- void print_desc (pretty_printer &pp) const final override
- {
- pp_string (&pp, m_summary.get_desc ().get ());
- }
-
-private:
- const function &m_called_fn;
- call_summary &m_summary;
- const extrinsic_state &m_ext_state;
-};
-
-/* Use PATH_CTXT to bifurcate, which when handled will add custom edges
- for a replay of the various feasible summaries in CALLED_FN_DATA. */
-
-exploded_node::on_stmt_flags
-exploded_node::replay_call_summaries (exploded_graph &eg,
- const supernode *snode,
- const gcall &call_stmt,
- program_state *state,
- path_context *path_ctxt,
- const function &called_fn,
- per_function_data &called_fn_data,
- region_model_context *ctxt)
-{
- logger *logger = eg.get_logger ();
- LOG_SCOPE (logger);
-
- /* Each summary will call bifurcate on the PATH_CTXT. */
- for (auto summary : called_fn_data.m_summaries)
- {
- gcc_assert (summary);
- replay_call_summary (eg, snode, call_stmt, state,
- path_ctxt, called_fn, *summary, ctxt);
- }
- path_ctxt->terminate_path ();
-
- return on_stmt_flags ();
-}
-
-/* Use PATH_CTXT to bifurcate, which when handled will add a
- custom edge for a replay of SUMMARY, if the summary's
- conditions are feasible based on the current state. */
-
-void
-exploded_node::replay_call_summary (exploded_graph &eg,
- const supernode *snode,
- const gcall &call_stmt,
- program_state *old_state,
- path_context *path_ctxt,
- const function &called_fn,
- call_summary &summary,
- region_model_context *ctxt)
-{
- logger *logger = eg.get_logger ();
- LOG_SCOPE (logger);
- gcc_assert (snode);
- gcc_assert (old_state);
-
- if (logger)
- logger->log ("using %s as summary for call to %qE from %qE",
- summary.get_desc ().get (),
- called_fn.decl,
- snode->get_function ()->decl);
- const extrinsic_state &ext_state = eg.get_ext_state ();
- const program_state &summary_end_state = summary.get_state ();
- if (logger)
- {
- pretty_printer *pp = logger->get_printer ();
-
- logger->start_log_line ();
- pp_string (pp, "callsite state: ");
- old_state->dump_to_pp (ext_state, true, false, pp);
- logger->end_log_line ();
-
- logger->start_log_line ();
- pp_string (pp, "summary end state: ");
- summary_end_state.dump_to_pp (ext_state, true, false, pp);
- logger->end_log_line ();
- }
-
- program_state new_state (*old_state);
-
- call_details cd (call_stmt, new_state.m_region_model, ctxt);
- call_summary_replay r (cd, called_fn, summary, ext_state);
-
- if (path_ctxt)
- path_ctxt->bifurcate
- (std::make_unique<call_summary_edge_info> (cd,
- called_fn,
- summary,
- ext_state));
-}
-
-
-/* Consider the effect of following superedge SUCC from this node.
-
- Return true if it's feasible to follow the edge, or false
- if it's infeasible.
-
- Examples: if it's the "true" branch within
- a CFG and we know the conditional is false, we know it's infeasible.
- If it's one of multiple interprocedual "return" edges, then only
- the edge back to the most recent callsite is feasible.
-
- Update NEXT_STATE accordingly (e.g. to record that a condition was
- true or false, or that the NULL-ness of a pointer has been checked,
- pushing/popping stack frames, etc).
-
- Update NEXT_POINT accordingly (updating the call string). */
-
-bool
-exploded_node::on_edge (exploded_graph &eg,
- const superedge *succ,
- program_point *next_point,
- program_state *next_state,
- uncertainty_t *uncertainty)
-{
- LOG_FUNC (eg.get_logger ());
-
- if (!next_point->on_edge (eg, succ))
- return false;
-
- if (!next_state->on_edge (eg, this, succ, uncertainty))
- return false;
-
- return true;
-}
-
-/* Verify that the stack at LONGJMP_POINT is still valid, given a call
- to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
- called in must still be valid.
-
- Caveat: this merely checks the call_strings in the points; it doesn't
- detect the case where a frame returns and is then called again. */
-
-static bool
-valid_longjmp_stack_p (const program_point &longjmp_point,
- const program_point &setjmp_point)
-{
- const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
- const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
-
- if (cs_at_longjmp.length () < cs_at_setjmp.length ())
- return false;
-
- /* Check that the call strings match, up to the depth of the
- setjmp point. */
- for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
- if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
- return false;
-
- return true;
-}
-
-/* A pending_diagnostic subclass for complaining about bad longjmps,
- where the enclosing function of the "setjmp" has returned (and thus
- the stack frame no longer exists). */
-
-class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
-{
-public:
- stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
- const program_point &setjmp_point)
- : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
- m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
- {}
-
- int get_controlling_option () const final override
- {
- return OPT_Wanalyzer_stale_setjmp_buffer;
- }
-
- bool emit (diagnostic_emission_context &ctxt) final override
- {
- return ctxt.warn ("%qs called after enclosing function of %qs has returned",
- get_user_facing_name (m_longjmp_call),
- get_user_facing_name (m_setjmp_call));
- }
-
- const char *get_kind () const final override
- { return "stale_jmp_buf"; }
-
- bool operator== (const stale_jmp_buf &other) const
- {
- return (&m_setjmp_call == &other.m_setjmp_call
- && &m_longjmp_call == &other.m_longjmp_call);
- }
+ bool operator== (const stale_jmp_buf &other) const
+ {
+ return (&m_setjmp_call == &other.m_setjmp_call
+ && &m_longjmp_call == &other.m_longjmp_call);
+ }
bool
- maybe_add_custom_events_for_superedge (const exploded_edge &eedge,
- checker_path *emission_path)
+ maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
+ checker_path *emission_path)
final override
{
/* Detect exactly when the stack first becomes invalid,
rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
const gcall &setjmp_call = rewind_info.get_setjmp_call ();
- const program_point &setjmp_point = rewind_info.get_setjmp_point ();
+ const program_point point_before_setjmp = rewind_info.get_point_before_setjmp ();
+ const program_point point_after_setjmp = rewind_info.get_point_after_setjmp ();
const program_point &longjmp_point = get_point ();
/* Verify that the setjmp's call_stack hasn't been popped. */
- if (!valid_longjmp_stack_p (longjmp_point, setjmp_point))
+ if (!valid_longjmp_stack_p (longjmp_point, point_after_setjmp))
{
ctxt->warn (std::make_unique<stale_jmp_buf> (setjmp_call,
longjmp_call,
- setjmp_point));
+ point_before_setjmp));
return;
}
gcc_assert (longjmp_point.get_stack_depth ()
- >= setjmp_point.get_stack_depth ());
+ >= point_after_setjmp.get_stack_depth ());
/* Update the state for use by the destination node. */
unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
new_region_model->on_longjmp (longjmp_call, setjmp_call,
- setjmp_point.get_stack_depth (), ctxt);
+ point_after_setjmp.get_stack_depth (), ctxt);
/* Detect leaks in the new state relative to the old state. */
program_state::detect_leaks (get_state (), *new_state, nullptr,
eg.get_ext_state (), ctxt);
-
- program_point next_point
- = program_point::after_supernode (setjmp_point.get_supernode (),
- setjmp_point.get_call_string ());
-
exploded_node *next
- = eg.get_or_create_node (next_point, *new_state, this);
+ = eg.get_or_create_node (point_after_setjmp, *new_state, this);
/* Create custom exploded_edge for a longjmp. */
if (next)
}
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override
+ const exploded_edge &eedge,
+ pending_diagnostic &) const final override
{
const exploded_node *dst_node = eedge.m_dest;
const program_point &dst_point = dst_node->get_point ();
}
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override
+ const exploded_edge &eedge,
+ pending_diagnostic &) const final override
{
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
Assume we have an out-edge flagged with EH leading to
code for dispatch to catch handlers. */
const program_point next_point
- = program_point::before_supernode (sedge->m_dest,
- sedge,
- iter_enode->get_point ().get_call_string ());
+ (sedge->m_dest,
+ iter_enode->get_point ().get_call_string ());
exploded_node *next_enode
= get_or_create_node (next_point,
iter_enode->get_state (),
{
/* Nested function in analysis: unwinding to
the callsite in the analysis (or beyond). */
- program_point unwound_point
- = program_point::after_supernode (cs.get_caller_node (), cs);
+ program_point unwound_point (cs.get_return_node_in_caller (), cs);
unwound_point.pop_from_call_stack ();
exploded_node *after_unwind_enode
void
exploded_node::on_throw (exploded_graph &eg,
const gcall &throw_call,
+ const program_point &after_throw_point,
program_state *new_state,
bool is_rethrow,
region_model_context *ctxt)
const svalue *tinfo_sval = cd.get_arg_svalue (1);
type = tinfo_sval->maybe_get_type_from_typeinfo ();
}
+
auto throw_edge_info
= std::make_unique<throw_custom_edge> (cd, type, is_rethrow);
throw_edge_info->update_model (model, nullptr, ctxt);
- program_point after_throw_point = get_point ().get_next ();
-
exploded_node *after_throw_enode
= eg.get_or_create_node (after_throw_point, *new_state, this,
/* Don't add to worklist; we process
eg.unwind_from_exception (*after_throw_enode, &throw_call, ctxt);
}
-/* Handle a gimple "resx" statement by adding eedges and enode.
- that unwind to the next eh_dispatch statement, if any. Only
- the final enode is added to the worklist. */
-
-void
-exploded_node::on_resx (exploded_graph &eg,
- const gresx &/*resx*/,
- program_state */*new_state*/,
- region_model_context *ctxt)
-{
- eg.unwind_from_exception (*this,
- nullptr,
- ctxt);
-}
-
-
/* Subroutine of exploded_graph::process_node for finding the successors
of the supernode for a function exit basic block.
{
LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
- gcc_assert (get_point ().get_supernode ()->return_p ());
+ gcc_assert (get_point ().get_supernode ()->exit_p ());
/* If we're not a "top-level" function, do nothing; pop_frame
will be called when handling the return superedge. */
uncertainty_t uncertainty;
impl_region_model_context ctxt (eg, this,
&old_state, &new_state, &uncertainty, nullptr,
- get_stmt ());
+ nullptr);
const svalue *result = nullptr;
new_state.m_region_model->pop_frame (nullptr, &result, &ctxt, nullptr);
program_state::detect_leaks (old_state, new_state, result,
}
}
-/* class dynamic_call_info_t : public custom_edge_info. */
+// class interprocedural_call : public custom_edge_info
-/* Implementation of custom_edge_info::update_model vfunc
- for dynamic_call_info_t.
+void
+interprocedural_call::print (pretty_printer *pp) const
+{
+ pp_string (pp, "call to ");
+ pp_gimple_stmt_1 (pp, &m_call_stmt, 0, (dump_flags_t)0);
+}
- Update state for a dynamically discovered call (or return), by pushing
- or popping the a frame for the appropriate function. */
+void
+interprocedural_call::get_dot_attrs (const char *&/*out_style*/,
+ const char *&out_color) const
+{
+ out_color = "red";
+}
bool
-dynamic_call_info_t::update_model (region_model *model,
- const exploded_edge *eedge,
- region_model_context *ctxt) const
+interprocedural_call::update_state (program_state *state,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const
{
- gcc_assert (eedge);
- if (m_is_returning_call)
- model->update_for_return_gcall (m_dynamic_call, ctxt);
- else
- {
- function *callee = eedge->m_dest->get_function ();
- model->update_for_gcall (m_dynamic_call, ctxt, callee);
- }
+ return update_model (state->m_region_model, eedge, ctxt);
+}
+
+bool
+interprocedural_call::update_model (region_model *model,
+ const exploded_edge */*eedge*/,
+ region_model_context *ctxt) const
+{
+ model->update_for_gcall (m_call_stmt, ctxt, &m_callee_fun);
return true;
}
-/* Implementation of custom_edge_info::add_events_to_path vfunc
- for dynamic_call_info_t. */
+void
+interprocedural_call::add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const
+{
+ pd.add_call_event (eedge, m_call_stmt, *emission_path);
+}
+
+// class interprocedural_return : public custom_edge_info
void
-dynamic_call_info_t::add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const
+interprocedural_return::print (pretty_printer *pp) const
{
- const exploded_node *src_node = eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
- const exploded_node *dest_node = eedge.m_dest;
- const program_point &dest_point = dest_node->get_point ();
- const int dest_stack_depth = dest_point.get_stack_depth ();
+ pp_string (pp, "return from ");
+ pp_gimple_stmt_1 (pp, &m_call_stmt, 0, (dump_flags_t)0);
+}
- if (m_is_returning_call)
- emission_path->add_event
- (std::make_unique<return_event> (eedge,
- event_loc_info (m_dynamic_call.location,
- dest_point.get_fndecl (),
- dest_stack_depth)));
- else
- emission_path->add_event
- (std::make_unique<call_event> (eedge,
- event_loc_info (m_dynamic_call.location,
- src_point.get_fndecl (),
- src_stack_depth)));
+void
+interprocedural_return::get_dot_attrs (const char *&/*out_style*/,
+ const char *&out_color) const
+{
+ out_color = "green";
+}
+
+bool
+interprocedural_return::update_state (program_state *state,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const
+{
+ return update_model (state->m_region_model, eedge, ctxt);
+}
+
+bool
+interprocedural_return::update_model (region_model *model,
+ const exploded_edge */*eedge*/,
+ region_model_context *ctxt) const
+{
+ model->update_for_return_gcall (m_call_stmt, ctxt);
+ return true;
+}
+
+void
+interprocedural_return::add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge,
+ pending_diagnostic &) const
+{
+ const program_point &dst_point = eedge.m_dest->get_point ();
+ emission_path->add_event
+ (std::make_unique<return_event>
+ (eedge,
+ event_loc_info (m_call_stmt.location,
+ dst_point.get_fndecl (),
+ dst_point.get_stack_depth ())));
}
/* class rewind_info_t : public custom_edge_info. */
void
rewind_info_t::add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const
+ const exploded_edge &eedge,
+ pending_diagnostic &) const
{
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
const char *constraint = "true";
if (m_sedge)
- switch (m_sedge->m_kind)
- {
- default:
- gcc_unreachable ();
- case SUPEREDGE_CFG_EDGE:
- break;
- case SUPEREDGE_CALL:
- color = "red";
- //constraint = "false";
- break;
- case SUPEREDGE_RETURN:
- color = "green";
- //constraint = "false";
- break;
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- style = "\"dotted\"";
- break;
- }
- if (m_custom_info)
{
- color = "red";
- style = "\"dotted\"";
+ if (m_sedge->get_op ())
+ style = "\"solid\"";
+ else
+ style = "\"dotted\"";
}
+ if (m_custom_info)
+ m_custom_info->get_dot_attrs (style, color);
pp_printf (pp,
(" [style=%s, color=%s, weight=%d, constraint=%s,"
" headlabel=\""),
style, color, weight, constraint);
+ pp_flush (pp);
if (m_sedge)
m_sedge->dump_label_to_pp (pp, false);
pp_printf (pp, "%s",
could_do_work_p () ? "(could do work)" : "DOES NO WORK");
- //pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
pp_printf (pp, "\"];\n");
}
return eedge_obj;
}
+const gimple *
+exploded_edge::maybe_get_stmt () const
+{
+ auto op = maybe_get_op ();
+ if (!op)
+ return nullptr;
+ return op->maybe_get_stmt ();
+}
+
+const operation *
+exploded_edge::maybe_get_op () const
+{
+ if (!m_sedge)
+ return nullptr;
+ return m_sedge->get_op ();
+}
+
/* struct stats. */
/* stats' ctor. */
stats::stats (int num_supernodes)
-: m_node_reuse_count (0),
+: m_num_nodes (0),
+ m_node_reuse_count (0),
m_node_reuse_after_merge_count (0),
m_num_supernodes (num_supernodes)
{
- for (int i = 0; i < NUM_POINT_KINDS; i++)
- m_num_nodes[i] = 0;
}
/* Log these stats in multiline form to LOGGER. */
stats::log (logger *logger) const
{
gcc_assert (logger);
- for (int i = 0; i < NUM_POINT_KINDS; i++)
- if (m_num_nodes[i] > 0)
- logger->log ("m_num_nodes[%s]: %i",
- point_kind_to_string (static_cast <enum point_kind> (i)),
- m_num_nodes[i]);
+ logger->log ("m_num_nodes: %i", m_num_nodes);
logger->log ("m_node_reuse_count: %i", m_node_reuse_count);
logger->log ("m_node_reuse_after_merge_count: %i",
m_node_reuse_after_merge_count);
void
stats::dump (FILE *out) const
{
- for (int i = 0; i < NUM_POINT_KINDS; i++)
- if (m_num_nodes[i] > 0)
- fprintf (out, "m_num_nodes[%s]: %i\n",
- point_kind_to_string (static_cast <enum point_kind> (i)),
- m_num_nodes[i]);
+ fprintf (out, "m_num_nodes: %i\n", m_num_nodes);
fprintf (out, "m_node_reuse_count: %i\n", m_node_reuse_count);
fprintf (out, "m_node_reuse_after_merge_count: %i\n",
m_node_reuse_after_merge_count);
if (m_num_supernodes > 0)
- fprintf (out, "PK_AFTER_SUPERNODE nodes per supernode: %.2f\n",
- (float)m_num_nodes[PK_AFTER_SUPERNODE] / (float)m_num_supernodes);
+ fprintf (out, "enodes per supernode: %.2f\n",
+ (float)m_num_nodes / (float)m_num_supernodes);
}
/* Return the total number of enodes recorded within this object. */
int
stats::get_total_enodes () const
{
- int result = 0;
- for (int i = 0; i < NUM_POINT_KINDS; i++)
- result += m_num_nodes[i];
- return result;
+ return m_num_nodes;
}
/* struct per_function_data. */
strongly_connected_components::
strongly_connected_components (const supergraph &sg, logger *logger)
-: m_sg (sg), m_per_node (m_sg.num_nodes ())
+: m_sg (sg), m_per_node (m_sg.m_nodes.length ())
{
LOG_SCOPE (logger);
auto_timevar tv (TV_ANALYZER_SCC);
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
m_per_node.quick_push (per_node_data ());
- for (int i = 0; i < m_sg.num_nodes (); i++)
- if (m_per_node[i].m_index == -1)
- strong_connect (i);
+ for (auto snode : m_sg.m_nodes)
+ if (m_per_node[snode->m_id].m_id == -1)
+ strong_connect (snode->m_id, logger);
if (0)
dump ();
DEBUG_FUNCTION void
strongly_connected_components::dump () const
{
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ fprintf (stderr, "Stack: [");
+ bool first = true;
+ for (auto i : m_stack)
+ {
+ if (first)
+ first = false;
+ else
+ fprintf (stderr, ", ");
+ fprintf (stderr, "%i", i);
+ }
+ fprintf (stderr, "]\n");
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
{
const per_node_data &v = m_per_node[i];
fprintf (stderr, "SN %i: index: %i lowlink: %i on_stack: %i\n",
- i, v.m_index, v.m_lowlink, v.m_on_stack);
+ i, v.m_id, v.m_lowlink, v.m_on_stack);
}
}
strongly_connected_components::to_json () const
{
auto scc_arr = std::make_unique<json::array> ();
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
scc_arr->append (std::make_unique<json::integer_number> (get_scc_id (i)));
return scc_arr;
}
SCC algorithm. */
void
-strongly_connected_components::strong_connect (unsigned index)
+strongly_connected_components::strong_connect (unsigned id,
+ logger *logger)
{
- supernode *v_snode = m_sg.get_node_by_index (index);
+ supernode *v_snode = m_sg.m_nodes[id];
+ if (!v_snode)
+ return;
/* Set the depth index for v to the smallest unused index. */
- per_node_data *v = &m_per_node[index];
- v->m_index = index;
- v->m_lowlink = index;
- m_stack.safe_push (index);
+ per_node_data *v = &m_per_node[id];
+ v->m_id = id;
+ v->m_lowlink = id;
+ m_stack.safe_push (id);
v->m_on_stack = true;
- index++;
+ id++;
/* Consider successors of v. */
unsigned i;
superedge *sedge;
FOR_EACH_VEC_ELT (v_snode->m_succs, i, sedge)
{
- if (sedge->get_kind () != SUPEREDGE_CFG_EDGE
- && sedge->get_kind () != SUPEREDGE_INTRAPROCEDURAL_CALL)
- continue;
supernode *w_snode = sedge->m_dest;
- per_node_data *w = &m_per_node[w_snode->m_index];
- if (w->m_index == -1)
+ per_node_data *w = &m_per_node[w_snode->m_id];
+ if (w->m_id == -1)
{
/* Successor w has not yet been visited; recurse on it. */
- strong_connect (w_snode->m_index);
+ strong_connect (w_snode->m_id, logger);
v->m_lowlink = MIN (v->m_lowlink, w->m_lowlink);
}
else if (w->m_on_stack)
/* Successor w is in stack S and hence in the current SCC
If w is not on stack, then (v, w) is a cross-edge in the DFS
tree and must be ignored. */
- v->m_lowlink = MIN (v->m_lowlink, w->m_index);
+ v->m_lowlink = MIN (v->m_lowlink, w->m_id);
}
}
/* If v is a root node, pop the stack and generate an SCC. */
- if (v->m_lowlink == v->m_index)
+ if (v->m_lowlink == v->m_id)
{
+ if (logger)
+ logger->log ("got SCC root node: SN %i", v->m_id);
per_node_data *w;
do {
- int idx = m_stack.pop ();
- w = &m_per_node[idx];
+ int id = m_stack.pop ();
+ w = &m_per_node[id];
w->m_on_stack = false;
+ if (logger)
+ logger->log (" popping SN %i", w->m_id);
} while (w != v);
}
}
return 1;
/* Neither are nullptr. */
gcc_assert (snode_a && snode_b);
- if (snode_a->m_index != snode_b->m_index)
- return snode_a->m_index - snode_b->m_index;
+ if (snode_a->m_bb->index != snode_b->m_bb->index)
+ return snode_a->m_bb->index - snode_b->m_bb->index;
+ if (snode_a->m_id != snode_b->m_id)
+ return snode_a->m_id - snode_b->m_id;
gcc_assert (snode_a == snode_b);
- /* Order within supernode via program point. */
- int within_snode_cmp
- = function_point::cmp_within_supernode (point_a.get_function_point (),
- point_b.get_function_point ());
- if (within_snode_cmp)
- return within_snode_cmp;
-
/* Otherwise, we ought to have the same program_point. */
gcc_assert (point_a == point_b);
m_purge_map (purge_map),
m_plan (plan),
m_diagnostic_manager (logger, ext_state.get_engine (), verbosity),
- m_global_stats (m_sg.num_nodes ()),
- m_functionless_stats (m_sg.num_nodes ()),
- m_PK_AFTER_SUPERNODE_per_snode (m_sg.num_nodes ())
+ m_global_stats (m_sg.m_nodes.length ()),
+ m_functionless_stats (m_sg.m_nodes.length ())
{
m_origin = get_or_create_node
(program_point::origin (*ext_state.get_model_manager ()),
program_state (ext_state), nullptr);
- for (int i = 0; i < m_sg.num_nodes (); i++)
- m_PK_AFTER_SUPERNODE_per_snode.quick_push (i);
}
/* exploded_graph's dtor. */
pp_string (pp, "call to tainted_args function");
};
- bool update_model (region_model *,
- const exploded_edge *,
- region_model_context *) const final override
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const final override
{
- /* No-op. */
+ function *fun = eedge->m_dest->get_function ();
+ gcc_assert (fun);
+ model->push_frame (*fun, nullptr, nullptr, ctxt);
return true;
}
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &) const final override
+ const exploded_edge &,
+ pending_diagnostic &) const final override
{
emission_path->add_event
(std::make_unique<tainted_args_function_custom_event>
return nullptr;
}
+ if (point.get_call_string ().calc_recursion_depth ()
+ > param_analyzer_max_recursion_depth)
+ {
+ if (logger)
+ logger->log ("rejecting node: recursion limit exceeded");
+ return nullptr;
+ }
+
auto_cfun sentinel (point.get_function ());
state.validate (get_ext_state ());
= get_or_create_per_program_point_data (point);
/* Consider merging state with another enode at this program_point. */
- if (flag_analyzer_state_merge)
+ if (flag_analyzer_state_merge && point.state_merge_at_p ())
{
exploded_node *existing_enode;
unsigned i;
/* Update per-program_point data. */
per_point_data->m_enodes.safe_push (node);
- const enum point_kind node_pk = node->get_point ().get_kind ();
- m_global_stats.m_num_nodes[node_pk]++;
- per_fn_stats->m_num_nodes[node_pk]++;
- per_cs_stats->m_num_nodes[node_pk]++;
-
- if (node_pk == PK_AFTER_SUPERNODE)
- m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+ m_global_stats.m_num_nodes++;
+ per_fn_stats->m_num_nodes++;
+ per_cs_stats->m_num_nodes++;
if (logger)
{
point.print (pp, f);
logger->end_log_line ();
logger->start_log_line ();
- pp_string (pp, "pruned_state: ");
- pruned_state.dump_to_pp (m_ext_state, true, false, pp);
+ pp_string (pp, "state: ");
+ ps.get_state ().dump_to_pp (m_ext_state, true, false, pp);
logger->end_log_line ();
}
if (per_call_string_data **slot = m_per_call_string_data.get (&cs))
return *slot;
- per_call_string_data *data = new per_call_string_data (cs, m_sg.num_nodes ());
+ per_call_string_data *data = new per_call_string_data (cs, m_sg.m_nodes.length ());
m_per_call_string_data.put (&data->m_key,
data);
return data;
pp_string (pp, "call to tainted field");
};
- bool update_model (region_model *,
- const exploded_edge *,
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
region_model_context *) const final override
{
- /* No-op. */
+ model->push_frame (*eedge->m_dest->get_function (),
+ nullptr, nullptr, nullptr);
return true;
}
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &) const final override
+ const exploded_edge &,
+ pending_diagnostic &) const final override
{
/* Show the field in the struct declaration, e.g.
"(1) field 'store' is marked with '__attribute__((tainted_args))'" */
{
exploded_node *node = m_worklist.take_next ();
gcc_assert (node->get_status () == exploded_node::status::worklist);
- gcc_assert (node->m_succs.length () == 0
- || node == m_origin);
if (logger)
logger->log ("next to process: EN: %i", node->m_index);
- /* If we have a run of nodes that are before-supernode, try merging and
+ /* If we have a run of nodes at the same point, try merging and
processing them together, rather than pairwise or individually. */
- if (flag_analyzer_state_merge && node != m_origin)
- if (maybe_process_run_of_before_supernode_enodes (node))
+ if (flag_analyzer_state_merge
+ && node->get_point ().state_merge_at_p ())
+ if (maybe_process_run_of_enodes (node))
goto handle_limit;
/* Avoid exponential explosions of nodes by attempting to merge
{
gcc_assert (node_2->get_status ()
== exploded_node::status::worklist);
- gcc_assert (node->m_succs.length () == 0);
- gcc_assert (node_2->m_succs.length () == 0);
-
gcc_assert (node != node_2);
if (logger)
handle_limit:
/* Impose a hard limit on the number of exploded nodes, to ensure
that the analysis terminates in the face of pathological state
- explosion (or bugs).
-
- Specifically, the limit is on the number of PK_AFTER_SUPERNODE
- exploded nodes, looking at supernode exit events.
-
- We use exit rather than entry since there can be multiple
- entry ENs, one per phi; the number of PK_AFTER_SUPERNODE ought
- to be equivalent to the number of supernodes multiplied by the
- number of states. */
- const int limit = m_sg.num_nodes () * param_analyzer_bb_explosion_factor;
- if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE] > limit)
- {
- if (logger)
- logger->log ("bailing out; too many nodes");
- warning_at (node->get_point ().get_location (),
- OPT_Wanalyzer_too_complex,
- "analysis bailed out early"
- " (%i 'after-snode' enodes; %i enodes)",
- m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE],
- m_nodes.length ());
- return;
- }
+ explosion (or bugs). */
+ if (const int limit
+ = m_sg.num_nodes () * param_analyzer_bb_explosion_factor)
+ if (m_global_stats.m_num_nodes > limit)
+ {
+ if (logger)
+ logger->log ("bailing out; too many nodes");
+ warning_at (node->get_point ().get_location (),
+ OPT_Wanalyzer_too_complex,
+ "analysis bailed out early"
+ " (%i enodes)",
+ m_nodes.length ());
+ return;
+ }
}
}
/* Attempt to process a consecutive run of sufficiently-similar nodes in
- the worklist at a CFG join-point (having already popped ENODE from the
- head of the worklist).
+ the worklist at a point flagged with state_merge_at_p (having already
+ popped ENODE from the head of the worklist).
- If ENODE's point is of the form (before-supernode, SNODE) and the next
- nodes in the worklist are a consecutive run of enodes of the same form,
- for the same supernode as ENODE (but potentially from different in-edges),
+ If we have a consecutive run of enodes in the worklist all of which have
+ a single out-edge where all these out-edges are supports_bulk_merge_p and
+ all have the same successor snode and call string, then
process them all together, setting their status to status::bulk_merged,
and return true.
Otherwise, return false, in which case ENODE must be processed in the
normal way.
When processing them all together, generate successor states based
- on phi nodes for the appropriate CFG edges, and then attempt to merge
+ on the edge op update_state_for_bulk_merger, and then attempt to merge
these states into a minimal set of merged successor states, partitioning
the inputs by merged successor state.
bool
exploded_graph::
-maybe_process_run_of_before_supernode_enodes (exploded_node *enode)
+maybe_process_run_of_enodes (exploded_node *enode)
{
/* A struct for tracking per-input state. */
struct item
};
gcc_assert (enode->get_status () == exploded_node::status::worklist);
- gcc_assert (enode->m_succs.length () == 0);
- const program_point &point = enode->get_point ();
+ const program_point &src_point = enode->get_point ();
+ const supernode *src_snode = src_point.get_supernode ();
- if (point.get_kind () != PK_BEFORE_SUPERNODE)
+ logger * const logger = get_logger ();
+ LOG_SCOPE (logger);
+
+ if (src_snode->m_succs.length () != 1)
return false;
- const supernode *snode = point.get_supernode ();
+ auto sedge = src_snode->m_succs[0];
- logger * const logger = get_logger ();
- LOG_SCOPE (logger);
+ if (!sedge->supports_bulk_merge_p ())
+ return false;
+
+ const supernode *dst_snode = src_snode->m_succs[0]->m_dest;
- /* Find a run of enodes in the worklist that are before the same supernode,
- but potentially from different in-edges. */
+ /* Find a run of enodes in the worklist that all have single out-sedges
+ go to the same supernode, all of which are bulk-mergeable (i.e. have
+ a simple single intraprocedural outcome). */
auto_vec <exploded_node *> enodes;
enodes.safe_push (enode);
while (exploded_node *enode_2 = m_worklist.peek_next ())
gcc_assert (enode_2->m_succs.length () == 0);
const program_point &point_2 = enode_2->get_point ();
+ const supernode *src_snode_2 = point_2.get_supernode ();
- if (point_2.get_kind () == PK_BEFORE_SUPERNODE
- && point_2.get_supernode () == snode
- && &point_2.get_call_string () == &point.get_call_string ())
- {
- enodes.safe_push (enode_2);
- m_worklist.take_next ();
- }
- else
+ if (src_snode_2->m_succs.length () != 1)
break;
+ auto sedge_2 = src_snode_2->m_succs[0];
+ if (sedge_2->m_dest != dst_snode)
+ break;
+ if (&point_2.get_call_string () != &src_point.get_call_string ())
+ break;
+ if (!sedge_2->supports_bulk_merge_p ())
+ break;
+
+ enodes.safe_push (enode_2);
+ m_worklist.take_next ();
}
/* If the only node is ENODE, then give up. */
return false;
if (logger)
- logger->log ("got run of %i enodes for SN: %i",
- enodes.length (), snode->m_index);
+ logger->log ("got run of %i bulk-mergable enodes going to SN: %i",
+ enodes.length (), dst_snode->m_id);
- /* All of these enodes have a shared successor point (even if they
- were for different in-edges). */
- program_point next_point (point.get_next ());
+ /* All of these enodes have a shared intraprocedural successor point
+ (even if they were for different in-edges). */
+ program_point next_point (sedge->m_dest,
+ src_point.get_call_string ());
/* Calculate the successor state for each enode in enodes. */
auto_delete_vec<item> items (enodes.length ());
const program_state &state = iter_enode->get_state ();
program_state *next_state = &it->m_processed_state;
next_state->validate (m_ext_state);
- const program_point &iter_point = iter_enode->get_point ();
- if (const superedge *iter_sedge = iter_point.get_from_edge ())
- {
- uncertainty_t uncertainty;
- impl_region_model_context ctxt (*this, iter_enode,
- &state, next_state,
- &uncertainty, nullptr, nullptr);
- const cfg_superedge *last_cfg_superedge
- = iter_sedge->dyn_cast_cfg_superedge ();
- if (last_cfg_superedge)
- next_state->m_region_model->update_for_phis
- (snode, last_cfg_superedge, &ctxt);
- }
+ gcc_assert (iter_enode->get_supernode ()->m_succs.length () == 1);
+ const superedge *iter_sedge = iter_enode->get_supernode ()->m_succs[0];
+ if (auto op = iter_sedge->get_op ())
+ op->update_state_for_bulk_merger (state, *next_state);
next_state->validate (m_ext_state);
}
{
exploded_node *next = next_enodes[it->m_merger_idx];
if (next)
- add_edge (it->m_input_enode, next, nullptr,
- false); /* no "work" is done during merger. */
+ {
+ gcc_assert (it->m_input_enode->get_supernode ()->m_succs.length ()
+ == 1);
+ const superedge *sedge
+ = it->m_input_enode->get_supernode ()->m_succs[0];
+ add_edge (it->m_input_enode, next, sedge,
+ false); /* no "work" is done during merger. */
+ }
it->m_input_enode->set_status (exploded_node::status::bulk_merged);
}
if (logger)
logger->log ("merged %i in-enodes into %i out-enode(s) at SN: %i",
- items.length (), merged_states.length (), snode->m_index);
+ items.length (), merged_states.length (), dst_snode->m_id);
return true;
}
-/* Return true if STMT must appear at the start of its exploded node, and
- thus we can't consolidate its effects within a run of other statements,
- where PREV_STMT was the previous statement. */
-
-static bool
-stmt_requires_new_enode_p (const gimple *stmt,
- const gimple *prev_stmt)
-{
- if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
- {
- const gcall &call = *call_stmt;
- /* Stop consolidating at calls to
- "__analyzer_dump_exploded_nodes", so they always appear at the
- start of an exploded_node. */
- if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
- 1))
- return true;
-
- /* sm-signal.cc injects an additional custom eedge at "signal" calls
- from the registration enode to the handler enode, separate from the
- regular next state, which defeats the "detect state change" logic
- in process_node. Work around this via special-casing, to ensure
- we split the enode immediately before any "signal" call. */
- if (is_special_named_call_p (call, "signal", 2, true))
- return true;
- }
-
- /* If we had a PREV_STMT with an unknown location, and this stmt
- has a known location, then if a state change happens here, it
- could be consolidated into PREV_STMT, giving us an event with
- no location. Ensure that STMT gets its own exploded_node to
- avoid this. */
- if (get_pure_location (prev_stmt->location) == UNKNOWN_LOCATION
- && get_pure_location (stmt->location) != UNKNOWN_LOCATION)
- return true;
-
- return false;
-}
-
-/* Return true if OLD_STATE and NEW_STATE are sufficiently different that
- we should split enodes and create an exploded_edge separating them
- (which makes it easier to identify state changes of intereset when
- constructing checker_paths). */
-
-static bool
-state_change_requires_new_enode_p (const program_state &old_state,
- const program_state &new_state)
-{
- /* Changes in dynamic extents signify creations of heap/alloca regions
- and resizings of heap regions; likely to be of interest in
- diagnostic paths. */
- if (old_state.m_region_model->get_dynamic_extents ()
- != new_state.m_region_model->get_dynamic_extents ())
- return true;
-
- /* Changes in sm-state are of interest. */
- int sm_idx;
- sm_state_map *smap;
- FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
- {
- const sm_state_map *old_smap = old_state.m_checker_states[sm_idx];
- const sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
- if (*old_smap != *new_smap)
- return true;
- }
-
- return false;
-}
-
-/* Create enodes and eedges for the function calls that doesn't have an
- underlying call superedge.
-
- Such case occurs when GCC's middle end didn't know which function to
- call but the analyzer does (with the help of current state).
-
- Some example such calls are dynamically dispatched calls to virtual
- functions or calls that happen via function pointer. */
-
-bool
-exploded_graph::maybe_create_dynamic_call (const gcall &call,
- tree fn_decl,
- exploded_node *node,
- program_state next_state,
- program_point &next_point,
- uncertainty_t *uncertainty,
- logger *logger)
-{
- LOG_FUNC (logger);
-
- const program_point *this_point = &node->get_point ();
- function *fun = DECL_STRUCT_FUNCTION (fn_decl);
- if (fun)
- {
- const supergraph &sg = this->get_supergraph ();
- supernode *sn_entry = sg.get_node_for_function_entry (*fun);
- supernode *sn_exit = sg.get_node_for_function_exit (*fun);
-
- program_point new_point
- = program_point::before_supernode (sn_entry,
- nullptr,
- this_point->get_call_string ());
-
- new_point.push_to_call_stack (sn_exit,
- next_point.get_supernode());
-
- /* 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 (new_point.get_call_string ().calc_recursion_depth ()
- > param_analyzer_max_recursion_depth)
- {
- if (logger)
- logger->log ("rejecting call edge: recursion limit exceeded");
- return false;
- }
-
- next_state.push_call (*this, node, call, uncertainty);
-
- if (next_state.m_valid)
- {
- if (logger)
- logger->log ("Discovered call to %s [SN: %i -> SN: %i]",
- function_name(fun),
- this_point->get_supernode ()->m_index,
- sn_entry->m_index);
-
- exploded_node *enode = get_or_create_node (new_point,
- next_state,
- node);
- if (enode)
- add_edge (node,enode, nullptr,
- false, /* No work is done by the call itself. */
- std::make_unique<dynamic_call_info_t> (call));
- return true;
- }
- }
- return false;
-}
-
-/* Subclass of path_context for use within exploded_graph::process_node,
- so that we can split states e.g. at "realloc" calls. */
-
-class impl_path_context : public path_context
-{
-public:
- impl_path_context (const program_state *cur_state,
- logger *logger)
- : m_cur_state (cur_state),
- m_logger (logger),
- m_terminate_path (false)
- {
- }
-
- bool bifurcation_p () const
- {
- return m_custom_eedge_infos.length () > 0;
- }
-
- const program_state &get_state_at_bifurcation () const
- {
- gcc_assert (m_state_at_bifurcation);
- return *m_state_at_bifurcation;
- }
-
- void
- bifurcate (std::unique_ptr<custom_edge_info> info) final override
- {
- if (m_logger)
- m_logger->log ("bifurcating path");
-
- if (m_state_at_bifurcation)
- /* Verify that the state at bifurcation is consistent when we
- split into multiple out-edges. */
- gcc_assert (*m_state_at_bifurcation == *m_cur_state);
- else
- /* Take a copy of the cur_state at the moment when bifurcation
- happens. */
- m_state_at_bifurcation
- = std::unique_ptr<program_state> (new program_state (*m_cur_state));
-
- /* Take ownership of INFO. */
- m_custom_eedge_infos.safe_push (info.release ());
- }
-
- void terminate_path () final override
- {
- if (m_logger)
- m_logger->log ("terminating path");
- m_terminate_path = true;
- }
-
- bool terminate_path_p () const final override
- {
- return m_terminate_path;
- }
-
- const vec<custom_edge_info *> & get_custom_eedge_infos ()
- {
- return m_custom_eedge_infos;
- }
-
-private:
- const program_state *m_cur_state;
-
- logger *m_logger;
-
- /* Lazily-created copy of the state before the split. */
- std::unique_ptr<program_state> m_state_at_bifurcation;
-
- auto_vec <custom_edge_info *> m_custom_eedge_infos;
-
- bool m_terminate_path;
-};
-
-/* A subclass of pending_diagnostic for complaining about jumps through NULL
- function pointers. */
-
-class jump_through_null : public pending_diagnostic_subclass<jump_through_null>
-{
-public:
- jump_through_null (const gcall &call)
- : m_call (call)
- {}
-
- const char *get_kind () const final override
- {
- return "jump_through_null";
- }
-
- bool operator== (const jump_through_null &other) const
- {
- return &m_call == &other.m_call;
- }
-
- int get_controlling_option () const final override
- {
- return OPT_Wanalyzer_jump_through_null;
- }
-
- bool emit (diagnostic_emission_context &ctxt) final override
- {
- return ctxt.warn ("jump through null pointer");
- }
-
- bool describe_final_event (pretty_printer &pp,
- const evdesc::final_event &) final override
- {
- pp_string (&pp, "jump through null pointer here");
- return true;
- }
-
-private:
- const gcall &m_call;
-};
-
/* The core of exploded_graph::process_worklist (the main analysis loop),
handling one node in the worklist.
/* Update cfun and input_location in case of an ICE: make it easier to
track down which source construct we're failing to handle. */
auto_cfun sentinel (node->get_function ());
- const gimple *stmt = point.get_stmt ();
- if (stmt)
- input_location = stmt->location;
+
+ input_location = node->get_location ();
const program_state &state = node->get_state ();
if (logger)
logger->end_log_line ();
}
- switch (point.get_kind ())
- {
- default:
- gcc_unreachable ();
- case PK_ORIGIN:
- /* This node exists to simplify finding the shortest path
- to an exploded_node. */
- break;
-
- case PK_BEFORE_SUPERNODE:
- {
- program_state next_state (state);
- uncertainty_t uncertainty;
+ /* Don't do anything for the origin enode; the initial population of the
+ worklist has already added successor enodes. */
+ if (point.get_supernode () == nullptr)
+ return;
- if (point.get_from_edge ())
- {
- impl_region_model_context ctxt (*this, node,
- &state, &next_state,
- &uncertainty, nullptr, nullptr);
- const cfg_superedge *last_cfg_superedge
- = point.get_from_edge ()->dyn_cast_cfg_superedge ();
- if (last_cfg_superedge)
- next_state.m_region_model->update_for_phis
- (node->get_supernode (),
- last_cfg_superedge,
- &ctxt);
- program_state::detect_leaks (state, next_state, nullptr,
- get_ext_state (), &ctxt);
- }
+ /* Specialcase for EXIT BBs, which don't have out-edges. */
+ if (point.get_supernode ()->exit_p ())
+ {
+ gcc_assert (point.get_supernode ()->m_succs.length () == 0);
- program_point next_point (point.get_next ());
- exploded_node *next = get_or_create_node (next_point, next_state, node);
- if (next)
- add_edge (node, next, nullptr,
- false); /* Assume no work is done at phi nodes. */
- }
- break;
- case PK_BEFORE_STMT:
- {
- /* Determine the effect of a run of one or more statements
- within one supernode, generating an edge to the program_point
- after the last statement that's processed.
-
- Stop iterating statements and thus consolidating into one enode
- when:
- - reaching the end of the statements in the supernode
- - if an sm-state-change occurs (so that it gets its own
- exploded_node)
- - if "-fanalyzer-fine-grained" is active
- - encountering certain statements must appear at the start of
- their enode (for which stmt_requires_new_enode_p returns true)
-
- Update next_state in-place, to get the result of the one
- or more stmts that are processed.
-
- Split the node in-place if an sm-state-change occurs, so that
- the sm-state-change occurs on an edge where the src enode has
- exactly one stmt, the one that caused the change. */
- program_state next_state (state);
-
- impl_path_context path_ctxt (&next_state, logger);
-
- bool could_have_done_work = false;
- uncertainty_t uncertainty;
- const supernode *snode = point.get_supernode ();
- unsigned stmt_idx;
- const gimple *prev_stmt = nullptr;
- for (stmt_idx = point.get_stmt_idx ();
- stmt_idx < snode->m_stmts.length ();
- stmt_idx++)
- {
- const gimple *stmt = snode->m_stmts[stmt_idx];
+ if (point.get_stack_depth () > 1)
+ {
+ /* Interprocedural return. */
+ auto &src_call_string = point.get_call_string ();
+
+ const call_string::element_t &top_of_stack
+ = src_call_string.get_top_of_stack ();
+ const call_string *dst_call_string = src_call_string.get_parent ();
+ const program_point dst_point
+ (top_of_stack.get_return_snode_in_caller (),
+ *dst_call_string);
+ auto edge_info
+ = std::make_unique<interprocedural_return> (top_of_stack.get_call_stmt ());
+
+ const program_state &src_state (node->get_state ());
+ program_state dst_state (src_state);
+ uncertainty_t uncertainty;
+ impl_region_model_context ctxt (*this, node,
+ &src_state, &dst_state, &uncertainty,
+ nullptr,
+ nullptr);
+ edge_info->update_state (&dst_state, nullptr, &ctxt);
+
+ program_state::detect_leaks (src_state, dst_state,
+ nullptr, get_ext_state (),
+ &ctxt);
+
+ if (exploded_node *next
+ = get_or_create_node (dst_point, dst_state, node))
+ add_edge (node, next, nullptr, false,
+ std::move (edge_info));
+ }
+ else
+ {
+ /* End of top-level of analysis for this function.
+ Detect leaks, and potentially create a function summary. */
+ node->detect_leaks (*this);
- if (stmt_idx > point.get_stmt_idx ())
- if (stmt_requires_new_enode_p (stmt, prev_stmt))
+ if (flag_analyzer_call_summaries
+ && point.get_call_string ().empty_p ())
+ {
+ /* TODO: create function summary
+ There can be more than one; each corresponds to a different
+ final enode in the function. */
+ if (logger)
{
- stmt_idx--;
- break;
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ logger->log_partial
+ ("would create function summary for %qE; state: ",
+ point.get_fndecl ());
+ state.dump_to_pp (m_ext_state, true, false, pp);
+ logger->end_log_line ();
}
- prev_stmt = stmt;
-
- program_state old_state (next_state);
-
- /* Process the stmt. */
- exploded_node::on_stmt_flags flags
- = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty,
- &could_have_done_work, &path_ctxt);
- node->m_num_processed_stmts++;
-
- /* If flags.m_terminate_path, stop analyzing; any nodes/edges
- will have been added by on_stmt (e.g. for handling longjmp). */
- if (flags.m_terminate_path)
- return;
-
- if (next_state.m_region_model)
- {
- impl_region_model_context ctxt (*this, node,
- &old_state, &next_state,
- &uncertainty, nullptr, stmt);
- program_state::detect_leaks (old_state, next_state, nullptr,
- get_ext_state (), &ctxt);
- }
-
- unsigned next_idx = stmt_idx + 1;
- program_point next_point
- = (next_idx < point.get_supernode ()->m_stmts.length ()
- ? program_point::before_stmt (point.get_supernode (), next_idx,
- point.get_call_string ())
- : program_point::after_supernode (point.get_supernode (),
- point.get_call_string ()));
- next_state = next_state.prune_for_point (*this, next_point, node,
- &uncertainty);
-
- if (flag_analyzer_fine_grained
- || state_change_requires_new_enode_p (old_state, next_state)
- || path_ctxt.bifurcation_p ()
- || path_ctxt.terminate_path_p ())
- {
- program_point split_point
- = program_point::before_stmt (point.get_supernode (),
- stmt_idx,
- point.get_call_string ());
- if (split_point != node->get_point ())
- {
- /* If we're not at the start of NODE, split the enode at
- this stmt, so we have:
- node -> split_enode
- so that when split_enode is processed the next edge
- we add will be:
- split_enode -> next
- and any state change will effectively occur on that
- latter edge, and split_enode will contain just stmt. */
- if (logger)
- logger->log ("getting split_enode");
- exploded_node *split_enode
- = get_or_create_node (split_point, old_state, node);
- if (!split_enode)
- return;
- /* "stmt" will be reprocessed when split_enode is
- processed. */
- node->m_num_processed_stmts--;
- if (logger)
- logger->log ("creating edge to split_enode");
- add_edge (node, split_enode, nullptr, could_have_done_work);
- return;
- }
- else
- /* If we're at the start of NODE, stop iterating,
- so that an edge will be created from NODE to
- (next_point, next_state) below. */
- break;
- }
- }
- unsigned next_idx = stmt_idx + 1;
- program_point next_point
- = (next_idx < point.get_supernode ()->m_stmts.length ()
- ? program_point::before_stmt (point.get_supernode (), next_idx,
- point.get_call_string ())
- : program_point::after_supernode (point.get_supernode (),
- point.get_call_string ()));
- if (path_ctxt.terminate_path_p ())
- {
- if (logger)
- logger->log ("not adding node: terminating path");
- }
- else
- {
- exploded_node *next
- = get_or_create_node (next_point, next_state, node);
- if (next)
- add_edge (node, next, nullptr, could_have_done_work);
- }
-
- /* If we have custom edge infos, "bifurcate" the state
- accordingly, potentially creating a new state/enode/eedge
- instances. For example, to handle a "realloc" call, we
- might split into 3 states, for the "failure",
- "resizing in place", and "moving to a new buffer" cases. */
- for (auto edge_info_iter : path_ctxt.get_custom_eedge_infos ())
- {
- /* Take ownership of the edge infos from the path_ctxt. */
- std::unique_ptr<custom_edge_info> edge_info (edge_info_iter);
- if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("bifurcating for edge: ");
- edge_info->print (logger->get_printer ());
- logger->end_log_line ();
- }
- program_state bifurcated_new_state
- (path_ctxt.get_state_at_bifurcation ());
-
- /* Apply edge_info to state. */
- impl_region_model_context
- bifurcation_ctxt (*this,
- node, // enode_for_diag
- &path_ctxt.get_state_at_bifurcation (),
- &bifurcated_new_state,
- nullptr, // uncertainty_t *uncertainty
- nullptr, // path_context *path_ctxt
- stmt);
- if (edge_info->update_state (&bifurcated_new_state,
- nullptr, /* no exploded_edge yet. */
- &bifurcation_ctxt))
- {
- if (exploded_node *next2
- = edge_info->create_enode
- (*this,
- next_point,
- std::move (bifurcated_new_state),
- node,
- &bifurcation_ctxt))
- {
- add_edge (node, next2, nullptr,
- true /* assume that work could be done */,
- std::move (edge_info));
- }
- }
- else
- {
- if (logger)
- logger->log ("infeasible state, not adding node");
- }
- }
- }
- break;
- case PK_AFTER_SUPERNODE:
- {
- bool found_a_superedge = false;
- bool is_an_exit_block = false;
- /* If this is an EXIT BB, detect leaks, and potentially
- create a function summary. */
- if (point.get_supernode ()->return_p ())
- {
- is_an_exit_block = true;
- node->detect_leaks (*this);
- if (flag_analyzer_call_summaries
- && point.get_call_string ().empty_p ())
- {
- /* TODO: create function summary
- There can be more than one; each corresponds to a different
- final enode in the function. */
- if (logger)
- {
- pretty_printer *pp = logger->get_printer ();
- logger->start_log_line ();
- logger->log_partial
- ("would create function summary for %qE; state: ",
- point.get_fndecl ());
- state.dump_to_pp (m_ext_state, true, false, pp);
- logger->end_log_line ();
- }
- per_function_data *per_fn_data
- = get_or_create_per_function_data (point.get_function ());
- per_fn_data->add_call_summary (node);
- }
- }
- /* Traverse into successors of the supernode. */
- int i;
- superedge *succ;
- FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
- {
- found_a_superedge = true;
- if (logger)
- {
- label_text succ_desc (succ->get_description (false));
- logger->log ("considering SN: %i -> SN: %i (%s)",
- succ->m_src->m_index, succ->m_dest->m_index,
- succ_desc.get ());
- }
-
- program_point next_point
- = program_point::before_supernode (succ->m_dest, succ,
- point.get_call_string ());
- program_state next_state (state);
- uncertainty_t uncertainty;
-
- /* Make use the current state and try to discover and analyse
- indirect function calls (a call that doesn't have an underlying
- cgraph edge representing call).
-
- Some examples of such calls are virtual function calls
- and calls that happen via a function pointer. */
- if (succ->m_kind == SUPEREDGE_INTRAPROCEDURAL_CALL
- && !(succ->get_any_callgraph_edge ()))
- {
- const gcall &call
- = *point.get_supernode ()->get_final_call ();
-
- impl_region_model_context ctxt (*this,
- node,
- &state,
- &next_state,
- &uncertainty,
- nullptr,
- point.get_stmt());
-
- region_model *model = state.m_region_model;
- bool call_discovered = false;
-
- if (tree fn_decl = model->get_fndecl_for_call (call, &ctxt))
- call_discovered = maybe_create_dynamic_call (call,
- fn_decl,
- node,
- next_state,
- next_point,
- &uncertainty,
- logger);
- if (!call_discovered)
- {
- /* Check for jump through nullptr. */
- if (tree fn_ptr = gimple_call_fn (&call))
- {
- const svalue *fn_ptr_sval
- = model->get_rvalue (fn_ptr, &ctxt);
- if (fn_ptr_sval->all_zeroes_p ())
- ctxt.warn
- (std::make_unique<jump_through_null> (call));
- }
-
- /* An unknown function or a special function was called
- at this point, in such case, don't terminate the
- analysis of the current function.
-
- The analyzer handles calls to such functions while
- analysing the stmt itself, so the function call
- must have been handled by the anlyzer till now. */
- exploded_node *next
- = get_or_create_node (next_point,
- next_state,
- node);
- if (next)
- add_edge (node, next, succ,
- true /* assume that work is done */);
- }
- }
-
- /* Ignore CFG edges in the sgraph flagged with EH whilst
- we're exploring the egraph.
- We only use these sedges in special-case logic for
- dealing with exception-handling. */
- if (auto cfg_sedge = succ->dyn_cast_cfg_superedge ())
- if (cfg_sedge->get_flags () & EDGE_EH)
- {
- if (logger)
- logger->log ("rejecting EH edge");
- continue;
- }
+ per_function_data *per_fn_data
+ = get_or_create_per_function_data (point.get_function ());
+ per_fn_data->add_call_summary (node);
+ }
+ }
- if (!node->on_edge (*this, succ, &next_point, &next_state,
- &uncertainty))
- {
- if (logger)
- logger->log ("skipping impossible edge to SN: %i",
- succ->m_dest->m_index);
- continue;
- }
- exploded_node *next = get_or_create_node (next_point, next_state,
- node);
- if (next)
- {
- add_edge (node, next, succ, false);
+ return;
+ }
- /* We might have a function entrypoint. */
- detect_infinite_recursion (next);
- }
- }
+ /* Traverse into successors of the supernode. */
+ int i;
+ superedge *succ;
+ FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
+ {
+ if (logger)
+ {
+ label_text succ_desc (succ->get_description (false));
+ logger->log ("considering SN: %i -> SN: %i (%s)",
+ succ->m_src->m_id, succ->m_dest->m_id,
+ succ_desc.get ());
+ }
- /* Return from the calls which doesn't have a return superedge.
- Such case occurs when GCC's middle end didn't knew which function to
- call but analyzer did. */
- if ((is_an_exit_block && !found_a_superedge)
- && (!point.get_call_string ().empty_p ()))
- {
- const call_string &cs = point.get_call_string ();
- program_point next_point
- = program_point::before_supernode (cs.get_caller_node (),
- nullptr,
- cs);
- program_state next_state (state);
- uncertainty_t uncertainty;
+ program_point next_point (succ->m_dest, point.get_call_string ());
+ program_state next_state (state);
+ uncertainty_t uncertainty;
- const gcall *call
- = next_point.get_supernode ()->get_returning_call ();
+ /* Find the outcome(s) of any operation on the edge. */
+ operation_context op_ctxt (*this, *node, *succ);
- if (call)
- next_state.returning_call (*this, node, *call, &uncertainty);
+ /* Skip EH edges. */
+ if (auto cfg_edge = succ->get_any_cfg_edge ())
+ if (cfg_edge->flags & EDGE_EH)
+ continue;
- if (next_state.m_valid)
- {
- next_point.pop_from_call_stack ();
- exploded_node *enode = get_or_create_node (next_point,
- next_state,
- node);
- if (enode)
- add_edge (node, enode, nullptr, false,
- std::make_unique<dynamic_call_info_t> (*call, true));
- }
- }
- }
- break;
+ if (auto op = succ->get_op ())
+ op->execute (op_ctxt);
+ else
+ {
+ /* No-op.
+ Unconditional goto to the dst point, which
+ must be in same function.
+ The supernode changes, but the callstring and
+ state do not change. */
+ if (logger)
+ logger->log ("handling no-op edge");
+ auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+ if (exploded_node *next
+ = get_or_create_node (dst_point,
+ node->get_state (),
+ node))
+ add_edge (node, next, succ, false);
+ }
}
}
return *slot;
else
{
- int num_supernodes = fn ? n_basic_blocks_for_fn (fn) : 0;
- /* not quite the num supernodes, but nearly. */
+ int num_supernodes = 0;
+ for (auto snode : m_sg.m_nodes)
+ if (snode->get_function () == fn)
+ ++num_supernodes;
stats *new_stats = new stats (num_supernodes);
m_per_function_stats.put (fn, new_stats);
return new_stats;
enodes_per_function.print (pp);
/* Accumulate number of enodes per supernode. */
- auto_vec<unsigned> enodes_per_supernode (m_sg.num_nodes ());
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ auto_vec<unsigned> enodes_per_supernode (m_sg.m_nodes.length ());
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
enodes_per_supernode.quick_push (0);
int i;
exploded_node *enode;
const supernode *iter_snode = enode->get_supernode ();
if (!iter_snode)
continue;
- enodes_per_supernode[iter_snode->m_index]++;
+ enodes_per_supernode[iter_snode->m_id]++;
}
/* Accumulate excess enodes per supernode. */
- auto_vec<unsigned> excess_enodes_per_supernode (m_sg.num_nodes ());
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ auto_vec<unsigned> excess_enodes_per_supernode (m_sg.m_nodes.length ());
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
excess_enodes_per_supernode.quick_push (0);
for (point_map_t::iterator iter = m_per_point_data.begin ();
iter != m_per_point_data.end (); ++iter)
if (!iter_snode)
continue;
const per_program_point_data *point_data = (*iter).second;
- excess_enodes_per_supernode[iter_snode->m_index]
+ excess_enodes_per_supernode[iter_snode->m_id]
+= point_data->m_excess_enodes;
}
bar_chart enodes_per_snode;
bar_chart excess_enodes_per_snode;
bool have_excess_enodes = false;
- for (int i = 0; i < m_sg.num_nodes (); i++)
+ for (int i = 0; i < m_sg.m_nodes.length (); i++)
{
- const supernode *iter_snode = m_sg.get_node_by_index (i);
+ const supernode *iter_snode = m_sg.m_nodes[i];
if (iter_snode->get_function () != fn)
continue;
pretty_printer tmp_pp;
pp_printf (&tmp_pp, "sn %i (bb %i)",
- iter_snode->m_index, iter_snode->m_bb->index);
+ iter_snode->m_id, iter_snode->m_bb->index);
enodes_per_snode.add_item (pp_formatted_text (&tmp_pp),
- enodes_per_supernode[iter_snode->m_index]);
+ enodes_per_supernode[iter_snode->m_id]);
const int num_excess
- = excess_enodes_per_supernode[iter_snode->m_index];
+ = excess_enodes_per_supernode[iter_snode->m_id];
excess_enodes_per_snode.add_item (pp_formatted_text (&tmp_pp),
num_excess);
if (num_excess)
fprintf (out, "function: %s\n", function_name (fn));
(*iter).second->dump (out);
}
-
- fprintf (out, "PK_AFTER_SUPERNODE per supernode:\n");
- for (unsigned i = 0; i < m_PK_AFTER_SUPERNODE_per_snode.length (); i++)
- fprintf (out, " SN %i: %3i\n", i, m_PK_AFTER_SUPERNODE_per_snode[i]);
-}
-
-void
-exploded_graph::dump_states_for_supernode (FILE *out,
- const supernode *snode) const
-{
- fprintf (out, "PK_AFTER_SUPERNODE nodes for SN: %i\n", snode->m_index);
- int i;
- exploded_node *enode;
- int state_idx = 0;
- FOR_EACH_VEC_ELT (m_nodes, i, enode)
- {
- const supernode *iter_snode = enode->get_supernode ();
- if (enode->get_point ().get_kind () == PK_AFTER_SUPERNODE
- && iter_snode == snode)
- {
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- enode->get_state ().dump_to_pp (m_ext_state, true, false, &pp);
- fprintf (out, "state %i: EN: %i\n %s\n",
- state_idx++, enode->m_index,
- pp_formatted_text (&pp));
- }
- }
- fprintf (out, "#exploded_node for PK_AFTER_SUPERNODE for SN: %i = %i\n",
- snode->m_index, state_idx);
}
/* Return a new json::object of the form
stats m_global_stats;
function_stat_map_t m_per_function_stats;
stats m_functionless_stats;
- call_string_data_map_t m_per_call_string_data;
- auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode; */
+ call_string_data_map_t m_per_call_string_data; */
return egraph_obj;
}
int i;
const exploded_edge *eedge;
FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
- {
- const exploded_node *dst_node = eedge->m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const gimple *stmt = dst_point.get_stmt ();
- if (stmt == search_stmt)
- {
- *out_idx = i;
- return true;
- }
- }
+ if (search_stmt->code == GIMPLE_PHI)
+ {
+ /* Each phis_for_edge_op instance handles multiple phi stmts
+ at once, so we have to special-case the search for a phi stmt. */
+ if (auto op = eedge->maybe_get_op ())
+ if (auto phis_op = op->dyn_cast_phis_for_edge_op ())
+ if (phis_op->defines_ssa_name_p (gimple_phi_result (search_stmt)))
+ {
+ *out_idx = i;
+ return true;
+ }
+ }
+ else
+ {
+ /* Non-phi stmt. */
+ if (const gimple *stmt = eedge->maybe_get_stmt ())
+ if (stmt == search_stmt)
+ {
+ *out_idx = i;
+ return true;
+ }
+ }
return false;
}
{
const exploded_node &src_enode = *eedge->m_src;
const program_point &src_point = src_enode.get_point ();
- const gimple *last_stmt
- = src_point.get_supernode ()->get_last_stmt ();
*out = std::make_unique<feasibility_problem> (edge_idx, *eedge,
- last_stmt,
std::move (rc));
}
return false;
{
const exploded_node &src_enode = *eedge->m_src;
const program_point &src_point = src_enode.get_point ();
- if (logger)
- {
- logger->start_log_line ();
- src_point.print (logger->get_printer (), format (false));
- logger->end_log_line ();
- }
-
- /* Update state for the stmts that were processed in each enode. */
- for (unsigned stmt_idx = 0; stmt_idx < src_enode.m_num_processed_stmts;
- stmt_idx++)
- {
- const gimple *stmt = src_enode.get_processed_stmt (stmt_idx);
-
- /* Update cfun and input_location in case of ICE: make it easier to
- track down which source construct we're failing to handle. */
- auto_cfun sentinel (src_point.get_function ());
- input_location = stmt->location;
-
- update_for_stmt (stmt);
- }
-
- const superedge *sedge = eedge->m_sedge;
- if (sedge)
- {
- if (logger)
- {
- label_text desc (sedge->get_description (false));
- logger->log (" sedge: SN:%i -> SN:%i %s",
- sedge->m_src->m_index,
- sedge->m_dest->m_index,
- desc.get ());
- }
-
- const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
- if (!m_model.maybe_update_for_edge (*sedge, last_stmt, ctxt, out_rc))
- {
- if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("rejecting due to region model: ");
- m_model.dump_to_pp (logger->get_printer (), true, false);
- logger->end_log_line ();
- }
- return false;
- }
+ if (logger)
+ {
+ logger->start_log_line ();
+ src_point.print (logger->get_printer (), format (false));
+ logger->end_log_line ();
}
+
+ if (eedge->m_custom_info)
+ eedge->m_custom_info->update_model (&m_model, eedge, ctxt);
else
{
- /* Special-case the initial eedge from the origin node to the
- initial function by pushing a frame for it. */
- if (src_point.get_kind () == PK_ORIGIN)
+ const superedge *sedge = eedge->m_sedge;
+ if (sedge)
{
- gcc_assert (eedge->m_src->m_index == 0);
- gcc_assert (eedge->m_dest->get_point ().get_kind ()
- == PK_BEFORE_SUPERNODE);
- function *fun = eedge->m_dest->get_function ();
- gcc_assert (fun);
- m_model.push_frame (*fun, nullptr, nullptr, ctxt);
if (logger)
- logger->log (" pushing frame for %qD", fun->decl);
+ {
+ label_text desc (sedge->get_description (false));
+ logger->log (" sedge: SN:%i -> SN:%i %s",
+ sedge->m_src->m_id,
+ sedge->m_dest->m_id,
+ desc.get ());
+ }
+
+ if (sedge->get_op ())
+ if (!sedge->get_op ()->execute_for_feasibility (*eedge,
+ *this,
+ ctxt,
+ out_rc))
+ {
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("rejecting due to region model: ");
+ m_model.dump_to_pp (logger->get_printer (), true, false);
+ logger->end_log_line ();
+ }
+ return false;
+ }
}
- else if (eedge->m_custom_info)
+ else
{
- eedge->m_custom_info->update_model (&m_model, eedge, ctxt);
+ /* Special-case the initial eedge from the origin node to the
+ initial function by pushing a frame for it. */
+ if (eedge->m_src->m_index == 0)
+ {
+ function *fun = eedge->m_dest->get_function ();
+ gcc_assert (fun);
+ m_model.push_frame (*fun, nullptr, nullptr, ctxt);
+ if (logger)
+ logger->log (" pushing frame for %qD", fun->decl);
+ }
}
}
- /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
- a PK_BEFORE_STMT, or a PK_AFTER_SUPERNODE if no stmts).
- This will typically not be associated with a superedge. */
- if (src_point.get_from_edge ())
- {
- const cfg_superedge *last_cfg_superedge
- = src_point.get_from_edge ()->dyn_cast_cfg_superedge ();
- const exploded_node &dst_enode = *eedge->m_dest;
- const unsigned dst_snode_idx = dst_enode.get_supernode ()->m_index;
- if (last_cfg_superedge)
- {
- if (logger)
- logger->log (" update for phis");
- m_model.update_for_phis (src_enode.get_supernode (),
- last_cfg_superedge,
- ctxt);
- /* If we've entering an snode that we've already visited on this
- epath, then we need do fix things up for loops; see the
- comment for store::loop_replay_fixup.
- Perhaps we should probably also verify the callstring,
- and track program_points, but hopefully doing it by supernode
- is good enough. */
- if (bitmap_bit_p (m_snodes_visited, dst_snode_idx))
- m_model.loop_replay_fixup (dst_enode.get_state ().m_region_model);
- }
- bitmap_set_bit (m_snodes_visited, dst_snode_idx);
- }
- return true;
-}
-/* Update this object for the effects of STMT. */
+ {
+ const exploded_node &dst_enode = *eedge->m_dest;
+ const unsigned dst_snode_idx = dst_enode.get_supernode ()->m_id;
+ bitmap_set_bit (m_snodes_visited, dst_snode_idx);
+ }
-void
-feasibility_state::update_for_stmt (const gimple *stmt)
-{
- if (const gassign *assign = dyn_cast <const gassign *> (stmt))
- m_model.on_assignment (assign, nullptr);
- else if (const gasm *asm_stmt = dyn_cast <const gasm *> (stmt))
- m_model.on_asm_stmt (asm_stmt, nullptr);
- else if (const gcall *call = dyn_cast <const gcall *> (stmt))
- {
- bool unknown_side_effects = m_model.on_call_pre (*call, nullptr);
- m_model.on_call_post (*call, unknown_side_effects, nullptr);
- }
- else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
- m_model.on_return (return_, nullptr);
+ return true;
}
/* Dump this object to PP. */
void dump_dot (graphviz_out *gv, const dump_args_t &args) const final override
{
- gv->println ("subgraph \"cluster_supernode_%i\" {", m_supernode->m_index);
+ gv->println ("subgraph \"cluster_supernode_%i\" {", m_supernode->m_id);
gv->indent ();
gv->println ("style=\"dashed\";");
gv->println ("label=\"SN: %i (bb: %i; scc: %i)\";",
- m_supernode->m_index, m_supernode->m_bb->index,
+ m_supernode->m_id, m_supernode->m_bb->index,
args.m_eg.get_scc_id (*m_supernode));
int i;
= *(const supernode_cluster * const *)p1;
const supernode_cluster *c2
= *(const supernode_cluster * const *)p2;
- return c1->m_supernode->m_index - c2->m_supernode->m_index;
+ return c1->m_supernode->m_id - c2->m_supernode->m_id;
}
private:
exploded_node *enode;
FOR_EACH_VEC_ELT (m_nodes, i, enode)
{
- if (const gimple *stmt = enode->get_stmt ())
- {
- if (get_pure_location (richloc.get_loc ()) == UNKNOWN_LOCATION)
- richloc.set_range (0, stmt->location, SHOW_RANGE_WITH_CARET);
- else
- richloc.add_range (stmt->location,
- SHOW_RANGE_WITHOUT_CARET,
- new enode_label (m_ext_state, enode));
- }
+ location_t loc = enode->get_location ();
+ if (get_pure_location (richloc.get_loc ()) == UNKNOWN_LOCATION)
+ richloc.set_range (0, loc, SHOW_RANGE_WITH_CARET);
+ else
+ richloc.add_range (loc,
+ SHOW_RANGE_WITHOUT_CARET,
+ new enode_label (m_ext_state, enode));
}
warning_at (&richloc, 0, "%i exploded nodes", m_nodes.length ());
}
/* Emit a warning at any call to "__analyzer_dump_exploded_nodes",
- giving the number of processed exploded nodes for "before-stmt",
- and the IDs of processed, merger, and worklist enodes.
+ giving the number of processed exploded nodes at the snode before
+ the call, and the IDs of processed, merger, and worklist enodes.
We highlight the count of *processed* enodes since this is of most
interest in DejaGnu tests for ensuring that state merger has
hash_set<const gimple *> seen;
FOR_EACH_VEC_ELT (m_nodes, i, enode)
{
- if (enode->get_point ().get_kind () != PK_BEFORE_STMT)
+ const supernode *snode = enode->get_supernode ();
+ if (!snode)
+ continue;
+ if (snode->m_succs.length () != 1)
+ continue;
+ const superedge *sedge = snode->m_succs[0];
+ if (!sedge->get_op ())
+ continue;
+ const call_and_return_op *op
+ = sedge->get_op ()->dyn_cast_call_and_return_op ();
+ if (!op)
continue;
+ const gcall &call = op->get_gcall ();
+ if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes", 1))
+ {
+ if (seen.contains (&call))
+ continue;
- if (const gimple *stmt = enode->get_stmt ())
- if (const gcall *call = dyn_cast <const gcall *> (stmt))
- if (is_special_named_call_p (*call, "__analyzer_dump_exploded_nodes",
- 1))
+ auto_vec<exploded_node *> processed_enodes;
+ auto_vec<exploded_node *> merger_enodes;
+ auto_vec<exploded_node *> worklist_enodes;
+ /* This is O(N^2). */
+ unsigned j;
+ exploded_node *other_enode;
+ FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
{
- if (seen.contains (stmt))
- continue;
-
- auto_vec<exploded_node *> processed_enodes;
- auto_vec<exploded_node *> merger_enodes;
- auto_vec<exploded_node *> worklist_enodes;
- /* This is O(N^2). */
- unsigned j;
- exploded_node *other_enode;
- FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
- {
- if (other_enode->get_point ().get_kind () != PK_BEFORE_STMT)
- continue;
- if (other_enode->get_stmt () == stmt)
- switch (other_enode->get_status ())
- {
- default:
- gcc_unreachable ();
- case exploded_node::status::worklist:
- worklist_enodes.safe_push (other_enode);
- break;
- case exploded_node::status::processed:
- processed_enodes.safe_push (other_enode);
- break;
- case exploded_node::status::merger:
- merger_enodes.safe_push (other_enode);
- break;
- }
- }
+ if (other_enode->get_supernode () == snode)
+ switch (other_enode->get_status ())
+ {
+ default:
+ gcc_unreachable ();
+ case exploded_node::status::worklist:
+ worklist_enodes.safe_push (other_enode);
+ break;
+ case exploded_node::status::processed:
+ processed_enodes.safe_push (other_enode);
+ break;
+ case exploded_node::status::merger:
+ merger_enodes.safe_push (other_enode);
+ break;
+ }
+ }
- pretty_printer pp;
- pp_character (&pp, '[');
- print_enode_indices (&pp, processed_enodes);
- if (merger_enodes.length () > 0)
- {
- pp_string (&pp, "] merger(s): [");
- print_enode_indices (&pp, merger_enodes);
- }
- if (worklist_enodes.length () > 0)
- {
- pp_string (&pp, "] worklist: [");
- print_enode_indices (&pp, worklist_enodes);
- }
- pp_character (&pp, ']');
-
- warning_n (stmt->location, 0, processed_enodes.length (),
- "%i processed enode: %s",
- "%i processed enodes: %s",
- processed_enodes.length (), pp_formatted_text (&pp));
- seen.add (stmt);
-
- /* If the argument is non-zero, then print all of the states
- of the various enodes. */
- tree t_arg = fold (gimple_call_arg (call, 0));
- if (TREE_CODE (t_arg) != INTEGER_CST)
- {
- error_at (call->location,
- "integer constant required for arg 1");
- return;
- }
- int i_arg = TREE_INT_CST_LOW (t_arg);
- if (i_arg)
+ pretty_printer pp;
+ pp_character (&pp, '[');
+ print_enode_indices (&pp, processed_enodes);
+ if (merger_enodes.length () > 0)
+ {
+ pp_string (&pp, "] merger(s): [");
+ print_enode_indices (&pp, merger_enodes);
+ }
+ if (worklist_enodes.length () > 0)
+ {
+ pp_string (&pp, "] worklist: [");
+ print_enode_indices (&pp, worklist_enodes);
+ }
+ pp_character (&pp, ']');
+
+ warning_n (call.location, 0, processed_enodes.length (),
+ "%i processed enode: %s",
+ "%i processed enodes: %s",
+ processed_enodes.length (), pp_formatted_text (&pp));
+ seen.add (&call);
+
+ /* If the argument is non-zero, then print all of the states
+ of the various enodes. */
+ tree t_arg = fold (gimple_call_arg (&call, 0));
+ if (TREE_CODE (t_arg) != INTEGER_CST)
+ {
+ error_at (snode->m_loc,
+ "integer constant required for arg 1");
+ return;
+ }
+ int i_arg = TREE_INT_CST_LOW (t_arg);
+ if (i_arg)
+ {
+ exploded_node *other_enode;
+ FOR_EACH_VEC_ELT (processed_enodes, j, other_enode)
{
- exploded_node *other_enode;
- FOR_EACH_VEC_ELT (processed_enodes, j, other_enode)
- {
- fprintf (stderr, "%i of %i: EN %i:\n",
- j + 1, processed_enodes.length (),
- other_enode->m_index);
- other_enode->dump_succs_and_preds (stderr);
- /* Dump state. */
- other_enode->get_state ().dump (m_ext_state, false);
- }
+ fprintf (stderr, "%i of %i: EN %i:\n",
+ j + 1, processed_enodes.length (),
+ other_enode->m_index);
+ other_enode->dump_succs_and_preds (stderr);
+ /* Dump state. */
+ other_enode->get_state ().dump (m_ext_state, false);
}
}
+ }
}
}
}
}
-/* A collection of classes for visualizing the callgraph in .dot form
- (as represented in the supergraph). */
-
-/* Forward decls. */
-class viz_callgraph_node;
-class viz_callgraph_edge;
-class viz_callgraph;
-class viz_callgraph_cluster;
-
-/* Traits for using "digraph.h" to visualize the callgraph. */
-
-struct viz_callgraph_traits
-{
- typedef viz_callgraph_node node_t;
- typedef viz_callgraph_edge edge_t;
- typedef viz_callgraph graph_t;
- struct dump_args_t
- {
- dump_args_t (const exploded_graph *eg) : m_eg (eg) {}
- const exploded_graph *m_eg;
- };
- typedef viz_callgraph_cluster cluster_t;
-};
-
-/* Subclass of dnode representing a function within the callgraph. */
-
-class viz_callgraph_node : public dnode<viz_callgraph_traits>
-{
- friend class viz_callgraph;
-
-public:
- viz_callgraph_node (function *fun, int index)
- : m_fun (fun), m_index (index), m_num_supernodes (0), m_num_superedges (0)
- {
- gcc_assert (fun);
- }
-
- void dump_dot (graphviz_out *gv, const dump_args_t &args) const final override
- {
- pretty_printer *pp = gv->get_pp ();
-
- dump_dot_id (pp);
- pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
- "lightgrey");
- pp_write_text_to_stream (pp);
-
- pp_printf (pp, "VCG: %i: %s", m_index, function_name (m_fun));
- pp_newline (pp);
-
- pp_printf (pp, "supernodes: %i\n", m_num_supernodes);
- pp_newline (pp);
-
- pp_printf (pp, "superedges: %i\n", m_num_superedges);
- pp_newline (pp);
-
- if (args.m_eg)
- {
- unsigned i;
- exploded_node *enode;
- unsigned num_enodes = 0;
- FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
- {
- if (enode->get_point ().get_function () == m_fun)
- num_enodes++;
- }
- pp_printf (pp, "enodes: %i\n", num_enodes);
- pp_newline (pp);
-
- // TODO: also show the per-callstring breakdown
- const exploded_graph::call_string_data_map_t *per_cs_data
- = args.m_eg->get_per_call_string_data ();
- for (exploded_graph::call_string_data_map_t::iterator iter
- = per_cs_data->begin ();
- iter != per_cs_data->end ();
- ++iter)
- {
- const call_string *cs = (*iter).first;
- //per_call_string_data *data = (*iter).second;
- num_enodes = 0;
- FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
- {
- if (enode->get_point ().get_function () == m_fun
- && &enode->get_point ().get_call_string () == cs)
- num_enodes++;
- }
- if (num_enodes > 0)
- {
- cs->print (pp);
- pp_printf (pp, ": %i\n", num_enodes);
- }
- }
-
- /* Show any summaries. */
- per_function_data *data = args.m_eg->get_per_function_data (m_fun);
- if (data)
- {
- pp_newline (pp);
- pp_printf (pp, "summaries: %i\n", data->m_summaries.length ());
- for (auto summary : data->m_summaries)
- {
- pp_printf (pp, "\nsummary: %s:\n", summary->get_desc ().get ());
- const extrinsic_state &ext_state = args.m_eg->get_ext_state ();
- const program_state &state = summary->get_state ();
- state.dump_to_pp (ext_state, false, true, pp);
- pp_newline (pp);
- }
- }
- }
-
- pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
- pp_string (pp, "\"];\n\n");
- pp_flush (pp);
- }
-
- void dump_dot_id (pretty_printer *pp) const
- {
- pp_printf (pp, "vcg_%i", m_index);
- }
-
-private:
- function *m_fun;
- int m_index;
- int m_num_supernodes;
- int m_num_superedges;
-};
-
-/* Subclass of dedge representing a callgraph edge. */
-
-class viz_callgraph_edge : public dedge<viz_callgraph_traits>
-{
-public:
- viz_callgraph_edge (viz_callgraph_node *src, viz_callgraph_node *dest)
- : dedge<viz_callgraph_traits> (src, dest)
- {}
-
- void dump_dot (graphviz_out *gv, const dump_args_t &) const
- final override
- {
- pretty_printer *pp = gv->get_pp ();
-
- const char *style = "\"solid,bold\"";
- const char *color = "black";
- int weight = 10;
- const char *constraint = "true";
-
- m_src->dump_dot_id (pp);
- pp_string (pp, " -> ");
- m_dest->dump_dot_id (pp);
- pp_printf (pp,
- (" [style=%s, color=%s, weight=%d, constraint=%s,"
- " headlabel=\""),
- style, color, weight, constraint);
- pp_printf (pp, "\"];\n");
- }
-};
-
-/* Subclass of digraph representing the callgraph. */
-
-class viz_callgraph : public digraph<viz_callgraph_traits>
-{
-public:
- viz_callgraph (const supergraph &sg);
-
- viz_callgraph_node *get_vcg_node_for_function (function *fun)
- {
- return *m_map.get (fun);
- }
-
- viz_callgraph_node *get_vcg_node_for_snode (supernode *snode)
- {
- return get_vcg_node_for_function (snode->m_fun);
- }
-
-private:
- hash_map<function *, viz_callgraph_node *> m_map;
-};
-
-/* Placeholder subclass of cluster. */
-
-class viz_callgraph_cluster : public cluster<viz_callgraph_traits>
-{
-};
-
-/* viz_callgraph's ctor. */
-
-viz_callgraph::viz_callgraph (const supergraph &sg)
-{
- cgraph_node *node;
- FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
- {
- function *fun = node->get_fun ();
- viz_callgraph_node *vcg_node
- = new viz_callgraph_node (fun, m_nodes.length ());
- m_map.put (fun, vcg_node);
- add_node (vcg_node);
- }
-
- unsigned i;
- superedge *sedge;
- FOR_EACH_VEC_ELT (sg.m_edges, i, sedge)
- {
- viz_callgraph_node *vcg_src = get_vcg_node_for_snode (sedge->m_src);
- if (vcg_src->m_fun)
- get_vcg_node_for_function (vcg_src->m_fun)->m_num_superedges++;
- if (sedge->dyn_cast_call_superedge ())
- {
- viz_callgraph_node *vcg_dest = get_vcg_node_for_snode (sedge->m_dest);
- viz_callgraph_edge *vcg_edge
- = new viz_callgraph_edge (vcg_src, vcg_dest);
- add_edge (vcg_edge);
- }
- }
-
- supernode *snode;
- FOR_EACH_VEC_ELT (sg.m_nodes, i, snode)
- {
- if (snode->m_fun)
- get_vcg_node_for_function (snode->m_fun)->m_num_supernodes++;
- }
-}
-
-/* Dump the callgraph to FILENAME. */
-
-static void
-dump_callgraph (const supergraph &sg, const char *filename,
- const exploded_graph *eg)
-{
- FILE *outf = fopen (filename, "w");
- if (!outf)
- return;
-
- // TODO
- viz_callgraph vcg (sg);
- vcg.dump_dot (filename, nullptr, viz_callgraph_traits::dump_args_t (eg));
-
- fclose (outf);
-}
-
-/* Dump the callgraph to "<srcfile>.callgraph.dot". */
-
-static void
-dump_callgraph (const supergraph &sg, const exploded_graph *eg)
-{
- auto_timevar tv (TV_ANALYZER_DUMP);
- char *filename = concat (dump_base_name, ".callgraph.dot", nullptr);
- dump_callgraph (sg, filename, eg);
- free (filename);
-}
-
/* Subclass of dot_annotator for implementing
- DUMP_BASE_NAME.supergraph-eg.dot, a post-analysis dump of the supergraph.
+ DUMP_BASE_NAME.supergraph.N.eg.dot, a post-analysis dump of the supergraph.
Annotate the supergraph nodes by printing the exploded nodes in concise
- form within them, next to their pertinent statements where appropriate,
- colorizing the exploded nodes based on sm-state.
+ form within them, colorizing the exploded nodes based on sm-state.
Also show saved diagnostics within the exploded nodes, giving information
on whether they were feasible, and, if infeasible, where the problem
was. */
exploded_graph_annotator (const exploded_graph &eg)
: m_eg (eg)
{
- /* Avoid O(N^2) by prepopulating m_enodes_per_snodes. */
- unsigned i;
- supernode *snode;
- FOR_EACH_VEC_ELT (eg.get_supergraph ().m_nodes, i, snode)
- m_enodes_per_snodes.safe_push (new auto_vec <exploded_node *> ());
+ /* Avoid O(N^2) by prepopulating m_enodes_per_snode_id. */
+ for (int i = 0; i < eg.get_supergraph ().m_nodes.length (); ++i)
+ m_enodes_per_snode_id.push_back (std::vector<exploded_node *> ());
exploded_node *enode;
+ unsigned i;
FOR_EACH_VEC_ELT (m_eg.m_nodes, i, enode)
if (enode->get_supernode ())
- m_enodes_per_snodes[enode->get_supernode ()->m_index]->safe_push (enode);
+ m_enodes_per_snode_id[enode->get_supernode ()->m_id].push_back (enode);
}
- /* Show exploded nodes for BEFORE_SUPERNODE points before N. */
- bool add_node_annotations (graphviz_out *gv, const supernode &n,
- bool within_table)
+ /* Show exploded nodes for N. */
+ void add_node_annotations (graphviz_out *gv, const supernode &n)
const final override
{
- if (!within_table)
- return false;
gv->begin_tr ();
pretty_printer *pp = gv->get_pp ();
- gv->begin_td ();
- pp_string (pp, "BEFORE");
- pp_printf (pp, " (scc: %i)", m_eg.get_scc_id (n));
- gv->end_td ();
-
- unsigned i;
- exploded_node *enode;
- bool had_enode = false;
- FOR_EACH_VEC_ELT (*m_enodes_per_snodes[n.m_index], i, enode)
+ if (m_enodes_per_snode_id[n.m_id].empty ())
+ pp_string (pp, "<TD BGCOLOR=\"red\">UNREACHED</TD>");
+ else
{
- gcc_assert (enode->get_supernode () == &n);
- const program_point &point = enode->get_point ();
- if (point.get_kind () != PK_BEFORE_SUPERNODE)
- continue;
- print_enode (gv, enode);
- had_enode = true;
+ /* Adding an empty TD here makes the actual enodes
+ be right-aligned and tightly packed, greatly
+ improving the readability of the graph. */
+ pp_string (pp, "<TD></TD>");
+ for (auto enode : m_enodes_per_snode_id[n.m_id])
+ {
+ gcc_assert (enode->get_supernode () == &n);
+ print_enode (gv, enode);
+ }
}
- if (!had_enode)
- pp_string (pp, "<TD BGCOLOR=\"red\">UNREACHED</TD>");
+
pp_flush (pp);
gv->end_tr ();
- return true;
}
- /* Show exploded nodes for STMT. */
- void add_stmt_annotations (graphviz_out *gv, const gimple *stmt,
- bool within_row)
- const final override
+ void
+ add_extra_objects (graphviz_out *gv) const final override
{
- if (!within_row)
- return;
pretty_printer *pp = gv->get_pp ();
- const supernode *snode
- = m_eg.get_supergraph ().get_supernode_for_stmt (stmt);
- unsigned i;
- exploded_node *enode;
- bool had_td = false;
- FOR_EACH_VEC_ELT (*m_enodes_per_snodes[snode->m_index], i, enode)
- {
- const program_point &point = enode->get_point ();
- if (point.get_kind () != PK_BEFORE_STMT)
- continue;
- if (point.get_stmt () != stmt)
- continue;
- print_enode (gv, enode);
- had_td = true;
- }
+ pp_string (pp, "en_0 [shape=none,margin=0,style=filled,label=<<TABLE><TR>");
+ print_enode (gv, m_eg.m_nodes[0]);
+ pp_string (pp, "</TR></TABLE>>];\n\n");
pp_flush (pp);
- if (!had_td)
- {
- gv->begin_td ();
- gv->end_td ();
- }
- }
-
- /* Show exploded nodes for AFTER_SUPERNODE points after N. */
- bool add_after_node_annotations (graphviz_out *gv, const supernode &n)
- const final override
- {
- gv->begin_tr ();
- pretty_printer *pp = gv->get_pp ();
-
- gv->begin_td ();
- pp_string (pp, "AFTER");
- gv->end_td ();
unsigned i;
- exploded_node *enode;
- FOR_EACH_VEC_ELT (*m_enodes_per_snodes[n.m_index], i, enode)
+ exploded_edge *eedge;
+ FOR_EACH_VEC_ELT (m_eg.m_edges, i, eedge)
{
- gcc_assert (enode->get_supernode () == &n);
- const program_point &point = enode->get_point ();
- if (point.get_kind () != PK_AFTER_SUPERNODE)
- continue;
- print_enode (gv, enode);
+ print_enode_port (pp, *eedge->m_src, "s");
+ pp_string (pp, " -> ");
+ print_enode_port (pp, *eedge->m_dest, "n");
+ dot::attr_list attrs;
+ attrs.add (dot::id ("style"), dot::id ("dotted"));
+ if (eedge->m_custom_info)
+ {
+ pretty_printer info_pp;
+ pp_format_decoder (&info_pp) = default_tree_printer;
+ eedge->m_custom_info->print (&info_pp);
+ attrs.add (dot::id ("label"),
+ dot::id (pp_formatted_text (&info_pp)));
+ }
+ dot::writer w (*pp);
+ attrs.print (w);
+ pp_newline (pp);
}
- pp_flush (pp);
- gv->end_tr ();
- return true;
}
private:
+ void
+ print_enode_port (pretty_printer *pp,
+ const exploded_node &enode,
+ const char *compass_pt) const
+ {
+ if (const supernode *snode = enode.get_supernode ())
+ pp_printf (pp, "node_%i:en_%i:%s",
+ snode->m_id, enode.m_index, compass_pt);
+ else
+ pp_printf (pp, "en_%i:%s",
+ enode.m_index, compass_pt);
+ }
+
/* Concisely print a TD element for ENODE, showing the index, status,
and any saved_diagnostics at the enode. Colorize it to show sm-state.
pretty_printer *pp = gv->get_pp ();
pp_printf (pp, "<TD BGCOLOR=\"%s\">",
enode->get_dot_fillcolor ());
- pp_printf (pp, "<TABLE BORDER=\"0\">");
+ pp_printf (pp, "<TABLE BORDER=\"0\" PORT=\"en_%i\">", enode->m_index);
gv->begin_trtd ();
pp_printf (pp, "EN: %i", enode->m_index);
switch (enode->get_status ())
p->m_eedge.m_sedge->dump (pp);
pp_write_text_as_html_like_dot_to_stream (pp);
gv->end_tdtr ();
- gv->begin_trtd ();
- pp_gimple_stmt_1 (pp, p->m_last_stmt, 0, (dump_flags_t)0);
- pp_write_text_as_html_like_dot_to_stream (pp);
- gv->end_tdtr ();
/* Ideally we'd print p->m_model here; see the notes above about
tooltips. */
}
}
const exploded_graph &m_eg;
- auto_delete_vec<auto_vec <exploded_node *> > m_enodes_per_snodes;
+ std::vector<std::vector <exploded_node *> > m_enodes_per_snode_id;
};
/* Implement -fdump-analyzer-json. */
logger *m_logger;
};
+static void
+maybe_dump_supergraph (const supergraph &sg, const char *name,
+ const dot_annotator *annotator = nullptr,
+ const exploded_graph *eg = nullptr)
+{
+ static int dump_idx = 0;
+ if (!flag_dump_analyzer_supergraph)
+ return;
+
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ std::string filename (dump_base_name);
+ filename += ".supergraph.";
+ filename += std::to_string (dump_idx++);
+ filename += ".";
+ filename += name;
+ filename += ".dot";
+ supergraph::dump_args_t args
+ ((enum supergraph_dot_flags)SUPERGRAPH_DOT_SHOW_BBS,
+ annotator,
+ eg);
+ sg.dump_dot (filename.c_str (), args);
+}
+
/* Run the analysis "engine". */
void
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
node->get_untransformed_body ();
+ region_model_manager mgr;
+
/* Create the supergraph. */
- supergraph sg (logger);
+ supergraph sg (mgr, logger);
- engine eng (&sg, logger);
+ maybe_dump_supergraph (sg, "original");
- state_purge_map *purge_map = nullptr;
+ sg.fixup_locations (logger);
+ maybe_dump_supergraph (sg, "fixup-locations");
+
+ engine eng (mgr, &sg, logger);
+
+ state_purge_map *purge_map = nullptr;
if (flag_analyzer_state_purge)
purge_map = new state_purge_map (sg, eng.get_model_manager (), logger);
- if (flag_dump_analyzer_supergraph)
+ if (flag_analyzer_simplify_supergraph)
{
- /* Dump supergraph pre-analysis. */
- auto_timevar tv (TV_ANALYZER_DUMP);
- char *filename = concat (dump_base_name, ".supergraph.dot", nullptr);
- supergraph::dump_args_t args ((enum supergraph_dot_flags)0, nullptr);
- sg.dump_dot (filename, args);
- free (filename);
+ sg.simplify (logger);
+ maybe_dump_supergraph (sg, "simplified");
}
+ sg.sort_nodes (logger);
+ maybe_dump_supergraph (sg, "sorted");
+
if (flag_dump_analyzer_state_purge)
{
auto_timevar tv (TV_ANALYZER_DUMP);
state_purge_annotator a (purge_map);
char *filename = concat (dump_base_name, ".state-purge.dot", nullptr);
- supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+ supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a, nullptr);
sg.dump_dot (filename, args);
free (filename);
}
eg.log_stats ();
- if (flag_dump_analyzer_callgraph)
- dump_callgraph (sg, &eg);
-
if (flag_dump_analyzer_supergraph)
{
/* Dump post-analysis form of supergraph. */
- auto_timevar tv (TV_ANALYZER_DUMP);
- char *filename = concat (dump_base_name, ".supergraph-eg.dot", nullptr);
exploded_graph_annotator a (eg);
- supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
- sg.dump_dot (filename, args);
- free (filename);
+ maybe_dump_supergraph (sg, "eg", &a, &eg);
}
if (flag_dump_analyzer_json)
: m_loc (loc), m_fndecl (fndecl), m_depth (depth)
{}
+ event_loc_info (const exploded_node *enode);
+ event_loc_info (const program_point &point);
+
location_t m_loc;
tree m_fndecl;
int m_depth;
#include "analyzer/sm.h"
#include "analyzer/program-state.h"
#include "analyzer/diagnostic-manager.h"
+#include "analyzer/region-model.h"
namespace ana {
program_state *new_state,
uncertainty_t *uncertainty,
path_context *path_ctxt,
-
- const gimple *stmt,
- stmt_finder *stmt_finder = nullptr,
-
+ const gimple *stmt = nullptr,
bool *out_could_have_done_work = nullptr);
impl_region_model_context (program_state *state,
uncertainty_t *uncertainty,
logger *logger = nullptr);
- bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder = nullptr) final override;
+ bool
+ warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&ploc) final override;
void add_note (std::unique_ptr<pending_note> pn) final override;
void add_event (std::unique_ptr<checker_event> event) final override;
void on_svalue_leak (const svalue *) override;
bool checking_for_infinite_loop_p () const override { return false; }
void on_unusable_in_infinite_loop () override {}
+ pending_location
+ get_pending_location_for_diag () const override;
+
exploded_graph *m_eg;
log_user m_logger;
exploded_node *m_enode_for_diag;
const program_state *m_old_state;
program_state *m_new_state;
const gimple *m_stmt;
- stmt_finder *m_stmt_finder;
const extrinsic_state &m_ext_state;
uncertainty_t *m_uncertainty;
path_context *m_path_ctxt;
exploded_graph::process_node called on it. */
merger,
- /* Node was processed by maybe_process_run_of_before_supernode_enodes. */
+ /* Node was processed by maybe_process_run_of_enodes. */
bulk_merged
};
static const char * status_to_str (enum status s);
std::unique_ptr<json::object> to_json (const extrinsic_state &ext_state) const;
- /* The result of on_stmt. */
- struct on_stmt_flags
- {
- on_stmt_flags () : m_terminate_path (false)
- {}
-
- static on_stmt_flags terminate_path ()
- {
- return on_stmt_flags (true);
- }
-
- /* Should we stop analyzing this path (on_stmt may have already
- added nodes/edges, e.g. when handling longjmp). */
- bool m_terminate_path : 1;
-
- private:
- on_stmt_flags (bool terminate_path)
- : m_terminate_path (terminate_path)
- {}
- };
-
- on_stmt_flags on_stmt (exploded_graph &eg,
- const supernode *snode,
- const gimple *stmt,
- program_state *state,
- uncertainty_t *uncertainty,
- bool *out_could_have_done_work,
- path_context *path_ctxt);
- void on_stmt_pre (exploded_graph &eg,
- const gimple *stmt,
- program_state *state,
- bool *out_terminate_path,
- bool *out_unknown_side_effects,
- region_model_context *ctxt);
- void on_stmt_post (const gimple *stmt,
- program_state *state,
- bool unknown_side_effects,
- region_model_context *ctxt);
-
- on_stmt_flags replay_call_summaries (exploded_graph &eg,
- const supernode *snode,
- const gcall &call_stmt,
- program_state *state,
- path_context *path_ctxt,
- const function &called_fn,
- per_function_data &called_fn_data,
- region_model_context *ctxt);
- void replay_call_summary (exploded_graph &eg,
- const supernode *snode,
- const gcall &call_stmt,
- program_state *state,
- path_context *path_ctxt,
- const function &called_fn,
- call_summary &summary,
- region_model_context *ctxt);
-
- bool on_edge (exploded_graph &eg,
- const superedge *succ,
- program_point *next_point,
- program_state *next_state,
- uncertainty_t *uncertainty);
void on_longjmp (exploded_graph &eg,
const gcall &call,
program_state *new_state,
region_model_context *ctxt);
void on_throw (exploded_graph &eg,
const gcall &call,
+ const program_point &after_throw_point,
program_state *new_state,
bool is_rethrow,
region_model_context *ctxt);
- void on_resx (exploded_graph &eg,
- const gresx &resx,
- program_state *new_state,
- region_model_context *ctxt);
void detect_leaks (exploded_graph &eg);
{
return get_point ().get_supernode ();
}
+ location_t get_location () const
+ {
+ return get_point ().get_location ();
+ }
function *get_function () const
{
return get_point ().get_function ();
{
return get_point ().get_stack_depth ();
}
- const gimple *get_stmt () const { return get_point ().get_stmt (); }
- const gimple *get_processed_stmt (unsigned idx) const;
const program_state &get_state () const { return m_ps.get_state (); }
bool could_do_work_p () const { return m_could_do_work_p; }
+ const gimple *maybe_get_stmt () const;
+ const operation *maybe_get_op () const;
+
private:
DISABLE_COPY_AND_ASSIGN (exploded_edge);
bool m_could_do_work_p;
};
-/* Extra data for an exploded_edge that represents dynamic call info ( calls
- that doesn't have an underlying superedge representing the call ). */
+/* Extra data for an exploded_edge that represents an interprocedural
+ call. */
-class dynamic_call_info_t : public custom_edge_info
+class interprocedural_call : public custom_edge_info
{
public:
- dynamic_call_info_t (const gcall &dynamic_call,
- const bool is_returning_call = false)
- : m_dynamic_call (dynamic_call),
- m_is_returning_call (is_returning_call)
+ interprocedural_call (const gcall &call_stmt,
+ function &callee_fun)
+ : m_call_stmt (call_stmt),
+ m_callee_fun (callee_fun)
{}
- void print (pretty_printer *pp) const final override
- {
- if (m_is_returning_call)
- pp_string (pp, "dynamic_return");
- else
- pp_string (pp, "dynamic_call");
- }
+ void print (pretty_printer *pp) const final override;
+
+ void get_dot_attrs (const char *&out_style,
+ const char *&out_color) const final override;
+
+ bool update_state (program_state *state,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const final override;
bool update_model (region_model *model,
const exploded_edge *eedge,
region_model_context *ctxt) const final override;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override;
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const final override;
+
private:
- const gcall &m_dynamic_call;
- const bool m_is_returning_call;
+ const gcall &m_call_stmt;
+ function &m_callee_fun;
};
+/* Extra data for an exploded_edge that represents an interprocedural
+ return. */
+
+class interprocedural_return : public custom_edge_info
+{
+public:
+ interprocedural_return (const gcall &call_stmt)
+ : m_call_stmt (call_stmt)
+ {}
+
+ void print (pretty_printer *pp) const final override;
+
+ void get_dot_attrs (const char *&out_style,
+ const char *&out_color) const final override;
+
+ bool update_state (program_state *state,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const final override;
+
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const final override;
+
+ void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const final override;
+
+private:
+ const gcall &m_call_stmt;
+};
/* Extra data for an exploded_edge that represents a rewind from a
longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */
region_model_context *ctxt) const final override;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override;
+ const exploded_edge &eedge,
+ pending_diagnostic &pd) const final override;
- const program_point &get_setjmp_point () const
+ program_point
+ get_point_before_setjmp () const
{
- const program_point &origin_point = get_enode_origin ()->get_point ();
-
- /* "origin_point" ought to be before the call to "setjmp". */
- gcc_assert (origin_point.get_kind () == PK_BEFORE_STMT);
-
- /* TODO: assert that it's the final stmt in its supernode. */
+ return get_enode_origin ()->get_point ();
+ }
- return origin_point;
+ program_point
+ get_point_after_setjmp () const
+ {
+ const program_point &origin_point = get_enode_origin ()->get_point ();
+ return program_point (m_setjmp_record.m_sedge->m_dest,
+ origin_point.get_call_string ());
}
const gcall &get_setjmp_call () const
int get_total_enodes () const;
- int m_num_nodes[NUM_POINT_KINDS];
+ int m_num_nodes;
int m_node_reuse_count;
int m_node_reuse_after_merge_count;
int m_num_supernodes;
auto_vec<call_summary *> m_summaries;
};
-
/* The strongly connected components of a supergraph.
In particular, this allows us to compute a partial ordering
of supernodes. */
struct per_node_data
{
per_node_data ()
- : m_index (-1), m_lowlink (-1), m_on_stack (false)
+ : m_id (-1), m_lowlink (-1), m_on_stack (false)
{}
- int m_index;
+ int m_id;
int m_lowlink;
bool m_on_stack;
};
- void strong_connect (unsigned index);
+ void strong_connect (unsigned index, logger *logger);
const supergraph &m_sg;
auto_vec<unsigned> m_stack;
void add_node (exploded_node *enode);
int get_scc_id (const supernode &snode) const
{
- return m_scc.get_scc_id (snode.m_index);
+ return m_scc.get_scc_id (snode.m_id);
}
std::unique_ptr<json::object> to_json () const;
const supernode *snode = enode->get_supernode ();
if (snode == nullptr)
return 0;
- return m_worklist.m_scc.get_scc_id (snode->m_index);
+ return m_worklist.m_scc.get_scc_id (snode->m_id);
}
const worklist &m_worklist;
void build_initial_worklist ();
void process_worklist ();
- bool maybe_process_run_of_before_supernode_enodes (exploded_node *node);
+ bool maybe_process_run_of_enodes (exploded_node *node);
void process_node (exploded_node *node);
- bool maybe_create_dynamic_call (const gcall &call,
- tree fn_decl,
- exploded_node *node,
- program_state next_state,
- program_point &next_point,
- uncertainty_t *uncertainty,
- logger *logger);
-
exploded_node *get_or_create_node (const program_point &point,
const program_state &state,
exploded_node *enode_for_diag,
void save_diagnostic (const state_machine &sm,
const exploded_node *enode,
const supernode *node, const gimple *stmt,
- stmt_finder *finder,
tree var, state_machine::state_t state,
pending_diagnostic *d);
stats *get_or_create_function_stats (function *fn);
void log_stats () const;
void dump_stats (FILE *) const;
- void dump_states_for_supernode (FILE *, const supernode *snode) const;
void dump_exploded_nodes () const;
std::unique_ptr<json::object> to_json () const;
call_string_data_map_t m_per_call_string_data;
- auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
-
/* Functions with a top-level enode, to make add_function_entry
be idempotent, for use in handling callbacks. */
hash_set<function *> m_functions_with_enodes;
public:
feasibility_problem (unsigned eedge_idx,
const exploded_edge &eedge,
- const gimple *last_stmt,
std::unique_ptr<rejected_constraint> rc)
: m_eedge_idx (eedge_idx), m_eedge (eedge),
- m_last_stmt (last_stmt), m_rc (std::move (rc))
+ m_rc (std::move (rc))
{}
void dump_to_pp (pretty_printer *pp) const;
unsigned m_eedge_idx;
const exploded_edge &m_eedge;
- const gimple *m_last_stmt;
std::unique_ptr<rejected_constraint> m_rc;
};
const exploded_edge *eedge,
region_model_context *ctxt,
std::unique_ptr<rejected_constraint> *out_rc);
- void update_for_stmt (const gimple *stmt);
+ region_model &get_model () { return m_model; }
const region_model &get_model () const { return m_model; }
+ auto_sbitmap &get_snodes_visited () { return m_snodes_visited; }
const auto_sbitmap &get_snodes_visited () const { return m_snodes_visited; }
void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
typedef shortest_paths<eg_traits, exploded_path> shortest_exploded_paths;
-/* Abstract base class for use when passing nullptr as the stmt for
- a possible warning, allowing the choice of stmt to be deferred
- until after we have an emission path (and know we're emitting a
- warning). */
-
-class stmt_finder
-{
-public:
- virtual ~stmt_finder () {}
- virtual std::unique_ptr<stmt_finder> clone () const = 0;
- virtual const gimple *find_stmt (const exploded_path &epath) = 0;
- virtual void update_event_loc_info (event_loc_info &) = 0;
-};
-
// TODO: split the above up?
} // namespace ana
m_state.get_model ().dump_to_pp (pp, true, true);
pp_newline (pp);
- m_inner_node->dump_processed_stmts (pp);
m_inner_node->dump_saved_diagnostics (pp);
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
pp_flush (pp);
}
-/* Attempt to get the region_model for this node's state at TARGET_STMT.
- Return true and write to *OUT if found.
- Return false if there's a problem. */
-
-bool
-feasible_node::get_state_at_stmt (const gimple *target_stmt,
- region_model *out) const
-{
- if (!target_stmt)
- return false;
-
- feasibility_state result (m_state);
-
- /* Update state for the stmts that were processed in each enode. */
- for (unsigned stmt_idx = 0; stmt_idx < m_inner_node->m_num_processed_stmts;
- stmt_idx++)
- {
- const gimple *stmt = m_inner_node->get_processed_stmt (stmt_idx);
- if (stmt == target_stmt)
- {
- *out = result.get_model ();
- return true;
- }
- result.update_for_stmt (stmt);
- }
-
- /* TARGET_STMT not found; wrong node? */
- return false;
-}
-
/* Implementation of dump_dot vfunc for infeasible_node.
In particular, show the rejected constraint. */
--- /dev/null
+/* Concrete implementation of sm_context.
+ 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_IMPL_SM_CONTEXT
+#define GCC_ANALYZER_IMPL_SM_CONTEXT
+
+namespace ana {
+
+/* Concrete implementation of sm_context, wiring it up to the rest of this
+ file. */
+
+class impl_sm_context : public sm_context
+{
+public:
+ impl_sm_context (exploded_graph &eg,
+ int sm_idx,
+ const state_machine &sm,
+ exploded_node *enode_for_diag,
+ const program_state *old_state,
+ program_state *new_state,
+ const sm_state_map *old_smap,
+ sm_state_map *new_smap,
+ path_context *path_ctxt,
+ bool unknown_side_effects = false)
+ : sm_context (sm_idx, sm),
+ m_logger (eg.get_logger ()),
+ m_eg (eg), m_enode_for_diag (enode_for_diag),
+ m_old_state (old_state), m_new_state (new_state),
+ m_old_smap (old_smap), m_new_smap (new_smap),
+ m_path_ctxt (path_ctxt),
+ m_unknown_side_effects (unknown_side_effects)
+ {
+ }
+
+ logger *get_logger () const { return m_logger.get_logger (); }
+
+ tree get_fndecl_for_call (const gcall &call) final override
+ {
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
+ nullptr, &call);
+ region_model *model = m_new_state->m_region_model;
+ return model->get_fndecl_for_call (call, &old_ctxt);
+ }
+
+ state_machine::state_t get_state (tree var) final override
+ {
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ /* Use nullptr ctxt on this get_rvalue call to avoid triggering
+ uninitialized value warnings. */
+ const svalue *var_old_sval
+ = m_old_state->m_region_model->get_rvalue (var, nullptr);
+
+ state_machine::state_t current
+ = m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
+ return current;
+ }
+ state_machine::state_t get_state (const svalue *sval) final override
+ {
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ state_machine::state_t current
+ = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+ return current;
+ }
+
+
+ void set_next_state (tree var,
+ state_machine::state_t to,
+ tree origin) final override
+ {
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ const svalue *var_new_sval
+ = m_new_state->m_region_model->get_rvalue (var, nullptr);
+ const svalue *origin_new_sval
+ = m_new_state->m_region_model->get_rvalue (origin, nullptr);
+
+ /* We use the new sval here to avoid issues with uninitialized values. */
+ state_machine::state_t current
+ = m_old_smap->get_state (var_new_sval, m_eg.get_ext_state ());
+ if (logger)
+ logger->log ("%s: state transition of %qE: %s -> %s",
+ m_sm.get_name (),
+ var,
+ current->get_name (),
+ to->get_name ());
+ m_new_smap->set_state (m_new_state->m_region_model, var_new_sval,
+ to, origin_new_sval, m_eg.get_ext_state ());
+ }
+
+ void set_next_state (const svalue *sval,
+ state_machine::state_t to,
+ tree origin) final override
+ {
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
+ nullptr, nullptr);
+
+ const svalue *origin_new_sval
+ = m_new_state->m_region_model->get_rvalue (origin, nullptr);
+
+ state_machine::state_t current
+ = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("%s: state transition of ",
+ m_sm.get_name ());
+ sval->dump_to_pp (logger->get_printer (), true);
+ logger->log_partial (": %s -> %s",
+ current->get_name (),
+ to->get_name ());
+ logger->end_log_line ();
+ }
+ m_new_smap->set_state (m_new_state->m_region_model, sval,
+ to, origin_new_sval, m_eg.get_ext_state ());
+ }
+
+ void warn (tree var,
+ std::unique_ptr<pending_diagnostic> d) final override
+ {
+ LOG_FUNC (get_logger ());
+ gcc_assert (d);
+ const svalue *var_old_sval
+ = m_old_state->m_region_model->get_rvalue (var, nullptr);
+ state_machine::state_t current
+ = (var
+ ? m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ())
+ : m_old_smap->get_global_state ());
+ bool terminate_path = d->terminate_path_p ();
+ pending_location ploc (m_enode_for_diag);
+ m_eg.get_diagnostic_manager ().add_diagnostic
+ (&m_sm, std::move (ploc),
+ var, var_old_sval, current, std::move (d));
+ if (m_path_ctxt
+ && terminate_path
+ && flag_analyzer_suppress_followups)
+ m_path_ctxt->terminate_path ();
+ }
+
+ void warn (const svalue *sval,
+ std::unique_ptr<pending_diagnostic> d) final override
+ {
+ LOG_FUNC (get_logger ());
+ gcc_assert (d);
+ state_machine::state_t current
+ = (sval
+ ? m_old_smap->get_state (sval, m_eg.get_ext_state ())
+ : m_old_smap->get_global_state ());
+ bool terminate_path = d->terminate_path_p ();
+ pending_location ploc (m_enode_for_diag);
+ m_eg.get_diagnostic_manager ().add_diagnostic
+ (&m_sm, std::move (ploc),
+ NULL_TREE, sval, current, std::move (d));
+ if (m_path_ctxt
+ && terminate_path
+ && flag_analyzer_suppress_followups)
+ m_path_ctxt->terminate_path ();
+ }
+
+ /* Hook for picking more readable trees for SSA names of temporaries,
+ so that rather than e.g.
+ "double-free of '<unknown>'"
+ we can print:
+ "double-free of 'inbuf.data'". */
+
+ tree get_diagnostic_tree (tree expr) final override
+ {
+ /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
+ likely to be the least surprising tree to report. */
+ if (TREE_CODE (expr) != SSA_NAME)
+ return expr;
+ if (SSA_NAME_VAR (expr) != NULL)
+ return expr;
+
+ gcc_assert (m_new_state);
+ const svalue *sval = m_new_state->m_region_model->get_rvalue (expr, nullptr);
+ /* Find trees for all regions storing the value. */
+ if (tree t = m_new_state->m_region_model->get_representative_tree (sval))
+ return t;
+ else
+ return expr;
+ }
+
+ tree get_diagnostic_tree (const svalue *sval) final override
+ {
+ return m_new_state->m_region_model->get_representative_tree (sval);
+ }
+
+ state_machine::state_t get_global_state () const final override
+ {
+ return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
+ }
+
+ void set_global_state (state_machine::state_t state) final override
+ {
+ m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
+ }
+
+ void clear_all_per_svalue_state () final override
+ {
+ m_new_state->m_checker_states[m_sm_idx]->clear_all_per_svalue_state ();
+ }
+
+ void on_custom_transition (custom_transition *transition) final override
+ {
+ transition->impl_transition (&m_eg,
+ const_cast<exploded_node *> (m_enode_for_diag),
+ m_sm_idx);
+ }
+
+ tree is_zero_assignment (const gimple *stmt) final override
+ {
+ const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+ if (!assign_stmt)
+ return NULL_TREE;
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, m_old_state, m_new_state, nullptr, nullptr, stmt);
+ if (const svalue *sval
+ = m_new_state->m_region_model->get_gassign_result (assign_stmt,
+ &old_ctxt))
+ if (tree cst = sval->maybe_get_constant ())
+ if (::zerop(cst))
+ return gimple_assign_lhs (assign_stmt);
+ return NULL_TREE;
+ }
+
+ path_context *get_path_context () const final override
+ {
+ return m_path_ctxt;
+ }
+
+ bool unknown_side_effects_p () const final override
+ {
+ return m_unknown_side_effects;
+ }
+
+ const program_state *get_old_program_state () const final override
+ {
+ return m_old_state;
+ }
+
+ const program_state *get_new_program_state () const final override
+ {
+ return m_new_state;
+ }
+
+ location_t get_emission_location () const final override
+ {
+ return pending_location (m_enode_for_diag).get_location ();
+ }
+
+ log_user m_logger;
+ exploded_graph &m_eg;
+ exploded_node *m_enode_for_diag;
+ const program_state *m_old_state;
+ program_state *m_new_state;
+ const sm_state_map *m_old_smap;
+ sm_state_map *m_new_smap;
+ path_context *m_path_ctxt;
+
+ /* Are we handling an external function with unknown side effects? */
+ bool m_unknown_side_effects;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_IMPL_SM_CONTEXT */
{
public:
perpetual_start_cfg_edge_event (const exploded_edge &eedge,
- const event_loc_info &loc_info)
- : start_cfg_edge_event (eedge, loc_info)
+ const event_loc_info &loc_info,
+ const control_flow_op *op)
+ : start_cfg_edge_event (eedge, loc_info, op)
{
}
label_text edge_desc (m_sedge->get_description (user_facing));
if (user_facing)
{
- if (edge_desc.get () && strlen (edge_desc.get ()) > 0)
+ if (edge_desc.get ()
+ && strlen (edge_desc.get ()) > 0
+ && m_op)
{
label_text cond_desc
- = maybe_describe_condition (pp_show_color (&pp));
+ = m_op->maybe_describe_condition (pp_show_color (&pp));
if (cond_desc.get ())
pp_printf (&pp,
"%s: always following %qs branch...",
{
public:
looping_back_event (const exploded_edge &eedge,
- const event_loc_info &loc_info)
- : start_cfg_edge_event (eedge, loc_info)
+ const event_loc_info &loc_info,
+ const control_flow_op *op)
+ : start_cfg_edge_event (eedge, loc_info, op)
{
}
return ctxt.warn ("infinite loop");
}
- bool maybe_add_custom_events_for_superedge (const exploded_edge &,
- checker_path *)
+ bool maybe_add_custom_events_for_eedge (const exploded_edge &,
+ checker_path *)
final override
{
/* Don't add any regular events; instead we add them after pruning as
if (!eedge->m_sedge)
continue;
- const cfg_superedge *cfg_sedge
- = eedge->m_sedge->dyn_cast_cfg_superedge ();
- if (!cfg_sedge)
+ ::edge cfg_edge = eedge->m_sedge->get_any_cfg_edge ();
+ if (!cfg_edge)
continue;
const exploded_node *src_node = eedge->m_src;
const program_point &dst_point = dst_node->get_point ();
const int src_stack_depth = src_point.get_stack_depth ();
const int dst_stack_depth = dst_point.get_stack_depth ();
- const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
-
event_loc_info loc_info_from
- (last_stmt ? last_stmt->location : cfg_sedge->get_goto_locus (),
+ (src_point.get_supernode ()->get_location (),
src_point.get_fndecl (),
src_stack_depth);
event_loc_info loc_info_to
- (dst_point.get_supernode ()->get_start_location (),
+ (dst_point.get_supernode ()->get_location (),
dst_point.get_fndecl (),
dst_stack_depth);
-
- if (const switch_cfg_superedge *switch_cfg_sedge
- = cfg_sedge->dyn_cast_switch_cfg_superedge ())
- {
- if (switch_cfg_sedge->implicitly_created_default_p ())
- {
- emission_path->add_event
- (std::make_unique<perpetual_start_cfg_edge_event>
+ if (auto base_op = eedge->m_sedge->get_op ())
+ if (auto control_flow_op = base_op->dyn_cast_control_flow_op ())
+ {
+ if (control_flow_op->get_kind () == operation::switch_edge)
+ {
+ const switch_case_op &case_op
+ = *(const switch_case_op *)base_op;
+ if (case_op.implicitly_created_default_p ())
+ {
+ emission_path->add_event
+ (std::make_unique<perpetual_start_cfg_edge_event>
+ (*eedge,
+ loc_info_from,
+ control_flow_op));
+ emission_path->add_event
+ (std::make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to,
+ control_flow_op));
+ }
+ }
+ else if (cfg_edge->flags & EDGE_TRUE_VALUE)
+ {
+ emission_path->add_event
+ (std::make_unique<perpetual_start_cfg_edge_event>
(*eedge,
- loc_info_from));
- emission_path->add_event
- (std::make_unique<end_cfg_edge_event>
+ loc_info_from,
+ control_flow_op));
+ emission_path->add_event
+ (std::make_unique<end_cfg_edge_event>
(*eedge,
- loc_info_to));
- }
- }
+ loc_info_to,
+ control_flow_op));
+ }
+ else if (cfg_edge->flags & EDGE_FALSE_VALUE)
+ {
+ emission_path->add_event
+ (std::make_unique<perpetual_start_cfg_edge_event>
+ (*eedge,
+ loc_info_from,
+ control_flow_op));
+ emission_path->add_event
+ (std::make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to,
+ control_flow_op));
+ }
+ }
- if (cfg_sedge->true_value_p ())
- {
- emission_path->add_event
- (std::make_unique<perpetual_start_cfg_edge_event>
- (*eedge,
- loc_info_from));
- emission_path->add_event
- (std::make_unique<end_cfg_edge_event>
- (*eedge,
- loc_info_to));
- }
- else if (cfg_sedge->false_value_p ())
+ if (cfg_edge->flags & EDGE_DFS_BACK)
{
emission_path->add_event
- (std::make_unique<perpetual_start_cfg_edge_event>
- (*eedge,
- loc_info_from));
+ (std::make_unique<looping_back_event> (*eedge, loc_info_from,
+ nullptr));
emission_path->add_event
(std::make_unique<end_cfg_edge_event>
(*eedge,
- loc_info_to));
- }
- else if (cfg_sedge->back_edge_p ())
- {
- emission_path->add_event
- (std::make_unique<looping_back_event> (*eedge, loc_info_from));
- emission_path->add_event
- (std::make_unique<end_cfg_edge_event>
- (*eedge,
- loc_info_to));
+ loc_info_to,
+ nullptr));
}
}
}
const superedge *sedge = in_edge->m_sedge;
if (!sedge)
continue;
- const cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ();
- if (!cfg_sedge)
- continue;
- if (cfg_sedge->back_edge_p ())
- return in_edge;
+ if (::edge cfg_in_edge = sedge->get_any_cfg_edge ())
+ if (cfg_in_edge->flags & EDGE_DFS_BACK)
+ return in_edge;
}
return nullptr;
}
visited.add (iter);
if (first_loc == UNKNOWN_LOCATION)
{
- location_t enode_loc = iter->get_point ().get_location ();
- if (enode_loc != UNKNOWN_LOCATION)
- first_loc = enode_loc;
+ auto snode = iter->get_supernode ();
+ gcc_assert (snode);
+ /* Ignore initial location in loop if it's a merger node,
+ to avoid using locations of phi nodes that are at the end
+ of the loop in the source. */
+ if (!(iter == &enode && snode->m_state_merger_node))
+ {
+ location_t enode_loc = snode->get_location ();
+ if (useful_location_p (enode_loc))
+ first_loc = enode_loc;
+ }
}
/* Find the out-edges that are feasible, given the
if (std::unique_ptr<infinite_loop> inf_loop
= starts_infinite_loop_p (*enode, *this, get_logger ()))
{
- const supernode *snode = enode->get_supernode ();
-
if (get_logger ())
get_logger ()->log ("EN: %i from starts_infinite_loop_p",
enode->m_index);
continue;
}
- pending_location ploc (enode, snode, inf_loop->m_loc);
+ pending_location ploc (enode, inf_loop->m_loc);
auto d
= std::make_unique<infinite_loop_diagnostic> (std::move (inf_loop));
- get_diagnostic_manager ().add_diagnostic (ploc, std::move (d));
+ get_diagnostic_manager ().add_diagnostic (std::move (ploc),
+ std::move (d));
}
}
}
emission_path->add_event
(std::make_unique<warning_event>
(event_loc_info (m_new_entry_enode->get_supernode
- ()->get_start_location (),
+ ()->get_location (),
m_callee_fndecl,
m_new_entry_enode->get_stack_depth ()),
enode,
/* Reject paths in which conjured svalues have affected control flow
since m_prev_entry_enode. */
- bool check_valid_fpath_p (const feasible_node &final_fnode,
- const gimple *)
+ bool check_valid_fpath_p (const feasible_node &final_fnode)
const final override
{
/* Reject paths in which calls with unknown side effects have occurred
const superedge *sedge = eedge->m_sedge;
if (!sedge)
return false;
- const cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ();
- if (!cfg_sedge)
+ auto op = sedge->get_op ();
+ if (!op)
return false;
- const gimple *last_stmt = sedge->m_src->get_last_stmt ();
- if (!last_stmt)
+ const control_flow_op *ctrlflow_op = op->dyn_cast_control_flow_op ();
+ if (!ctrlflow_op)
return false;
+ const gimple &last_stmt = ctrlflow_op->get_ctrlflow_stmt ();
+
const feasible_node *dst_fnode
= static_cast<const feasible_node *> (fedge->m_dest);
const region_model &model = dst_fnode->get_state ().get_model ();
- if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+ if (const gcond *cond_stmt = dyn_cast <const gcond *> (&last_stmt))
{
if (expr_uses_conjured_svalue_p (model, gimple_cond_lhs (cond_stmt)))
return true;
return true;
}
else if (const gswitch *switch_stmt
- = dyn_cast <const gswitch *> (last_stmt))
+ = dyn_cast <const gswitch *> (&last_stmt))
{
if (expr_uses_conjured_svalue_p (model,
gimple_switch_index (switch_stmt)))
const checker_event *m_prev_entry_event;
};
-/* Return true iff ENODE is the PK_BEFORE_SUPERNODE at a function
- entrypoint. */
+/* Return true iff ENODE is at a function entrypoint. */
static bool
is_entrypoint_p (exploded_node *enode)
const supernode *snode = enode->get_supernode ();
if (!snode)
return false;
- if (!snode->entry_p ())
- return false;;
- const program_point &point = enode->get_point ();
- if (point.get_kind () != PK_BEFORE_SUPERNODE)
- return false;
- return true;
+ return snode->entry_p ();
}
/* Walk backwards through the eg, looking for the first
/* Otherwise, the state of memory is effectively the same between the two
recursion levels; warn. */
-
- const supernode *caller_snode = call_string.get_top_of_stack ().m_caller;
- const supernode *snode = enode->get_supernode ();
- gcc_assert (caller_snode->m_returning_call);
pending_location ploc (enode,
- snode,
- caller_snode->m_returning_call,
- nullptr);
+ call_string.get_top_of_stack ().get_call_stmt ().location);
get_diagnostic_manager ().add_diagnostic
- (ploc,
+ (std::move (ploc),
std::make_unique<infinite_recursion_diagnostic> (prev_entry_enode,
enode,
fndecl));
&& POINTER_TYPE_P (cd.get_arg_type (1)));
}
+ void
+ check_any_preconditions (const call_details &cd) const final override
+ {
+ region_model_context *ctxt = cd.get_ctxt ();
+ if (!ctxt)
+ return;
+ region_model *model = cd.get_model ();
+ const gcall &call = cd.get_call_stmt ();
+
+ /* If the call was actually a placement new, check that accessing
+ the buffer lhs is placed into does not result in out-of-bounds. */
+ if (is_placement_new_p (call))
+ {
+ if (const region *sized_reg = get_sized_region_for_placement_new (cd))
+ model->check_region_for_write (sized_reg,
+ nullptr,
+ ctxt);
+ }
+ }
+
void impl_call_pre (const call_details &cd) const final override
{
region_model *model = cd.get_model ();
region_model_context *ctxt = cd.get_ctxt ();
const gcall &call = cd.get_call_stmt ();
- /* If the call was actually a placement new, check that accessing
- the buffer lhs is placed into does not result in out-of-bounds. */
if (is_placement_new_p (call))
{
const region *ptr_reg = cd.deref_ptr_arg (1);
if (ptr_reg && cd.get_lhs_type ())
- {
- const svalue *num_bytes_sval = cd.get_arg_svalue (0);
- const region *sized_new_reg
- = mgr->get_sized_region (ptr_reg,
- cd.get_lhs_type (),
- num_bytes_sval);
- model->check_region_for_write (sized_new_reg,
- nullptr,
- ctxt);
- const svalue *ptr_sval
- = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_new_reg);
- cd.maybe_set_lhs (ptr_sval);
- }
+ if (const region *sized_reg = get_sized_region_for_placement_new (cd))
+ {
+ const svalue *ptr_sval
+ = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_reg);
+ cd.maybe_set_lhs (ptr_sval);
+ }
}
/* If the call is an allocating new, then create a heap allocated
region. */
model->add_constraint (result, NE_EXPR, null_sval, ctxt);
}
}
+
+private:
+ const region *
+ get_sized_region_for_placement_new (const call_details &cd) const
+ {
+ const region *ptr_reg = cd.deref_ptr_arg (1);
+ if (ptr_reg && cd.get_lhs_type ())
+ {
+ region_model_manager *mgr = cd.get_manager ();
+ const svalue *num_bytes_sval = cd.get_arg_svalue (0);
+ return mgr->get_sized_region (ptr_reg,
+ cd.get_lhs_type (),
+ num_bytes_sval);
+ }
+ return nullptr;
+ }
};
/* Handler for "operator delete" and for "operator delete []",
--- /dev/null
+/* Operations within the code being analyzed.
+ 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/>. */
+
+#include "analyzer/common.h"
+
+#include "gimple-pretty-print.h"
+#include "gimple-iterator.h"
+#include "tree-cfg.h"
+#include "tree-dfa.h"
+#include "fold-const.h"
+#include "cgraph.h"
+#include "text-art/dump.h"
+#include "text-art/tree-widget.h"
+
+#include "analyzer/ops.h"
+#include "analyzer/call-details.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/impl-sm-context.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/call-summary.h"
+#include "analyzer/call-info.h"
+#include "analyzer/analysis-plan.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+event_loc_info::event_loc_info (const exploded_node *enode)
+{
+ if (enode)
+ {
+ m_loc = enode->get_location ();
+ m_fndecl = enode->get_point ().get_fndecl ();
+ m_depth = enode->get_stack_depth ();
+ }
+ else
+ {
+ m_loc = UNKNOWN_LOCATION;
+ m_fndecl = NULL_TREE;
+ m_depth = 0;
+ }
+}
+
+event_loc_info::event_loc_info (const program_point &point)
+{
+ m_loc = point.get_location ();
+ m_fndecl = point.get_fndecl ();
+ m_depth = point.get_stack_depth ();
+}
+
+// struct operation_context
+
+void
+operation_context::dump () const
+{
+ fprintf (stderr, "src enode: EN: %i\n", m_src_enode.m_index);
+ m_src_enode.dump (m_eg.get_ext_state ());
+
+ fprintf (stderr, "superedge\n");
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ m_sedge.dump (&pp);
+}
+
+logger *
+operation_context::get_logger () const
+{
+ return m_eg.get_logger ();
+}
+
+const extrinsic_state &
+operation_context::get_ext_state () const
+{
+ return m_eg.get_ext_state ();
+}
+
+const program_point &
+operation_context::get_initial_point () const
+{
+ return m_src_enode.get_point ();
+}
+
+const program_state &
+operation_context::get_initial_state () const
+{
+ return m_src_enode.get_state ();
+}
+
+const supergraph &
+operation_context::get_supergraph () const
+{
+ return m_eg.get_supergraph ();
+}
+
+program_point
+operation_context::get_next_intraprocedural_point () const
+{
+ /* All edges are intraprocedural. */
+ gcc_assert (m_sedge.m_src->get_function ()
+ == m_sedge.m_dest->get_function ());
+ return program_point (m_sedge.m_dest,
+ m_src_enode.get_point ().get_call_string ());
+}
+
+void
+operation_context::add_outcome (const program_point &dst_point,
+ program_state dst_state,
+ bool could_do_work,
+ uncertainty_t *uncertainty,
+ std::unique_ptr<custom_edge_info> info)
+{
+ const program_state &src_state = get_initial_state ();
+ impl_region_model_context ctxt (m_eg, &m_src_enode,
+ &src_state, &dst_state,
+ uncertainty, nullptr);
+ program_state::detect_leaks (src_state, dst_state, nullptr,
+ get_ext_state (), &ctxt);
+
+ if (exploded_node *dst_enode
+ = m_eg.get_or_create_node (dst_point, dst_state, &m_src_enode))
+ {
+ m_eg.add_edge (&m_src_enode, dst_enode, &m_sedge, could_do_work,
+ std::move (info));
+ m_eg.detect_infinite_recursion (dst_enode);
+ }
+}
+
+class op_region_model_context : public impl_region_model_context
+{
+public:
+ op_region_model_context (operation_context &op_ctxt,
+ program_state &dst_state)
+ : impl_region_model_context (op_ctxt.m_eg,
+ &op_ctxt.m_src_enode,
+ &op_ctxt.get_initial_state (),
+ &dst_state,
+ nullptr,
+ &m_path_context)
+ {
+ }
+
+ bool terminate_path_p () const
+ {
+ return m_path_context.terminate_path_p ();
+ }
+
+private:
+ class op_path_context : public path_context
+ {
+ public:
+ op_path_context ()
+ : m_terminate_path (false)
+ {
+ }
+
+ void bifurcate (std::unique_ptr<custom_edge_info>) final override
+ {
+ gcc_unreachable ();
+ }
+
+ void terminate_path () final override
+ {
+ m_terminate_path = true;
+ }
+
+ bool terminate_path_p () const final override
+ {
+ return m_terminate_path;
+ }
+ private:
+ bool m_terminate_path;
+ } m_path_context;
+};
+
+// class gimple_stmt_op : public operation
+
+void
+gimple_stmt_op::print_as_edge_label (pretty_printer *pp,
+ bool /*user_facing*/) const
+{
+ pp_gimple_stmt_1 (pp, &m_stmt, 0, (dump_flags_t)0);
+}
+
+bool
+gimple_stmt_op::defines_ssa_name_p (const_tree ssa_name) const
+{
+ return &m_stmt == SSA_NAME_DEF_STMT (ssa_name);
+}
+
+bool
+gimple_stmt_op::supports_bulk_merge_p () const
+{
+ return false;
+}
+
+/* Subclass of path_context for use within operation::execute implementations
+ so that we can split states e.g. at "realloc" calls. */
+
+class impl_path_context : public path_context
+{
+public:
+ impl_path_context (const program_state *cur_state,
+ logger *logger)
+ : m_cur_state (cur_state),
+ m_logger (logger),
+ m_terminate_path (false)
+ {
+ }
+
+ bool bifurcation_p () const
+ {
+ return m_custom_eedge_infos.length () > 0;
+ }
+
+ const program_state &get_state_at_bifurcation () const
+ {
+ gcc_assert (m_state_at_bifurcation);
+ return *m_state_at_bifurcation;
+ }
+
+ void
+ bifurcate (std::unique_ptr<custom_edge_info> info) final override
+ {
+ if (m_logger)
+ m_logger->log ("bifurcating path");
+
+ if (m_state_at_bifurcation)
+ /* Verify that the state at bifurcation is consistent when we
+ split into multiple out-edges. */
+ gcc_assert (*m_state_at_bifurcation == *m_cur_state);
+ else
+ /* Take a copy of the cur_state at the moment when bifurcation
+ happens. */
+ m_state_at_bifurcation
+ = std::unique_ptr<program_state> (new program_state (*m_cur_state));
+
+ /* Take ownership of INFO. */
+ m_custom_eedge_infos.safe_push (info.release ());
+ }
+
+ void terminate_path () final override
+ {
+ if (m_logger)
+ m_logger->log ("terminating path");
+ m_terminate_path = true;
+ }
+
+ bool terminate_path_p () const final override
+ {
+ return m_terminate_path;
+ }
+
+ const vec<custom_edge_info *> & get_custom_eedge_infos ()
+ {
+ return m_custom_eedge_infos;
+ }
+
+private:
+ const program_state *m_cur_state;
+
+ logger *m_logger;
+
+ /* Lazily-created copy of the state before the split. */
+ std::unique_ptr<program_state> m_state_at_bifurcation;
+
+ auto_vec <custom_edge_info *> m_custom_eedge_infos;
+
+ bool m_terminate_path;
+};
+
+DEBUG_FUNCTION void
+operation::dump () const
+{
+ tree_dump_pretty_printer pp (stderr);
+ print_as_edge_label (&pp, false);
+ pp_newline (&pp);
+}
+
+void
+operation::handle_on_stmt_for_state_machines (operation_context &op_ctxt,
+ program_state &dst_state,
+ path_context *path_ctxt,
+ bool &unknown_side_effects,
+ const gimple &stmt)
+{
+ const program_state &old_state = op_ctxt.get_initial_state ();
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx);
+ const sm_state_map *old_smap
+ = old_state.m_checker_states[sm_idx];
+ sm_state_map *new_smap = dst_state.m_checker_states[sm_idx];
+ impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm,
+ &op_ctxt.m_src_enode,
+ &old_state,
+ &dst_state,
+ old_smap, new_smap, path_ctxt,
+ unknown_side_effects);
+
+ /* Allow the state_machine to handle the stmt. */
+ if (sm.on_stmt (sm_ctxt, &stmt))
+ unknown_side_effects = false;
+ }
+}
+
+void
+gimple_stmt_op::
+walk_load_store_addr_ops (void *data,
+ walk_stmt_load_store_addr_fn load_cb,
+ walk_stmt_load_store_addr_fn store_cb,
+ walk_stmt_load_store_addr_fn addr_cb) const
+{
+ walk_stmt_load_store_addr_ops (const_cast<gimple *>(&m_stmt), data,
+ load_cb, store_cb, addr_cb);
+}
+
+void
+gimple_stmt_op::execute (operation_context &op_ctxt) const
+{
+ auto logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+ if (logger)
+ {
+ logger->start_log_line ();
+ pp_gimple_stmt_1 (logger->get_printer (), &get_stmt (), 0,
+ (dump_flags_t)0);
+ logger->end_log_line ();
+ }
+ execute_on_state (op_ctxt,
+ /* Pass in a copy. */
+ op_ctxt.get_initial_state ());
+}
+
+void
+gimple_stmt_op::execute_on_state (operation_context &op_ctxt,
+ program_state dst_state) const
+{
+ auto logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+
+ auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+ const program_state &old_state = op_ctxt.get_initial_state ();
+
+ bool unknown_side_effects = false;
+ bool could_have_done_work = false;
+
+ impl_path_context path_ctxt (&dst_state, logger);
+ uncertainty_t uncertainty;
+ impl_region_model_context ctxt (op_ctxt.m_eg,
+ &op_ctxt.m_src_enode,
+ &old_state,
+ &dst_state,
+ &uncertainty,
+ &path_ctxt,
+ &m_stmt,
+ &could_have_done_work);
+
+ dst_state.m_region_model->on_stmt_pre (&get_stmt (),
+ &unknown_side_effects,
+ &ctxt);
+
+ handle_on_stmt_for_state_machines (op_ctxt,
+ dst_state,
+ &path_ctxt,
+ unknown_side_effects,
+ m_stmt);
+
+ if (path_ctxt.terminate_path_p ())
+ return;
+
+ if (const gcall *call = dyn_cast <const gcall *> (&m_stmt))
+ dst_state.m_region_model->on_call_post (*call, unknown_side_effects, &ctxt);
+
+ if (!path_ctxt.terminate_path_p ())
+ op_ctxt.add_outcome (dst_point, dst_state, could_have_done_work,
+ &uncertainty);
+
+ /* If we have custom edge infos, "bifurcate" the state
+ accordingly, potentially creating a new state/enode/eedge
+ instances. For example, to handle a "realloc" call, we
+ might split into 3 states, for the "failure",
+ "resizing in place", and "moving to a new buffer" cases. */
+ for (auto edge_info_iter : path_ctxt.get_custom_eedge_infos ())
+ {
+ /* Take ownership of the edge infos from the path_ctxt. */
+ std::unique_ptr<custom_edge_info> edge_info (edge_info_iter);
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("bifurcating for edge: ");
+ edge_info->print (logger->get_printer ());
+ logger->end_log_line ();
+ }
+ program_state bifurcated_new_state
+ (path_ctxt.get_state_at_bifurcation ());
+
+ /* Apply edge_info to state. */
+ impl_region_model_context
+ bifurcation_ctxt (op_ctxt.m_eg,
+ &op_ctxt.m_src_enode,
+ &path_ctxt.get_state_at_bifurcation (),
+ &bifurcated_new_state,
+ nullptr, // uncertainty_t *uncertainty
+ nullptr, // path_context *path_ctxt
+ &m_stmt);
+ if (edge_info->update_state (&bifurcated_new_state,
+ nullptr, /* no exploded_edge yet. */
+ &bifurcation_ctxt))
+ {
+ if (exploded_node *next2
+ = edge_info->create_enode
+ (op_ctxt.m_eg,
+ dst_point,
+ std::move (bifurcated_new_state),
+ &op_ctxt.m_src_enode,
+ &bifurcation_ctxt))
+ {
+ op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode, next2, nullptr,
+ true /* assume that work could be done */,
+ std::move (edge_info));
+ }
+ }
+ }
+}
+
+bool
+gimple_stmt_op::
+execute_for_feasibility (const exploded_edge &,
+ feasibility_state &fstate,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+ region_model &model = fstate.get_model ();
+ bool unknown_side_effects;
+ model.on_stmt_pre (&m_stmt, &unknown_side_effects, ctxt);
+
+ if (const gcall *call = dyn_cast <const gcall *> (&m_stmt))
+ model.on_call_post (*call, unknown_side_effects, ctxt);
+
+ return true;
+}
+
+/* An sm_context for adding state_change_event on assignments to NULL,
+ where the default state isn't m_start. Storing such state in the
+ sm_state_map would lead to bloat of the exploded_graph, so we want
+ to leave it as a default state, and inject state change events here
+ when we have a diagnostic.
+ Find transitions of constants, for handling on_zero_assignment. */
+
+struct null_assignment_sm_context : public sm_context
+{
+ null_assignment_sm_context (int sm_idx,
+ const state_machine &sm,
+ const program_state *old_state,
+ const program_state *new_state,
+ const gimple *stmt,
+ const program_point *point,
+ checker_path *emission_path,
+ const extrinsic_state &ext_state)
+ : sm_context (sm_idx, sm), m_old_state (old_state), m_new_state (new_state),
+ m_stmt (stmt), m_point (point), m_emission_path (emission_path),
+ m_ext_state (ext_state)
+ {
+ }
+
+ tree get_fndecl_for_call (const gcall &/*call*/) final override
+ {
+ return NULL_TREE;
+ }
+
+ state_machine::state_t get_state (tree var) final override
+ {
+ const svalue *var_old_sval
+ = m_old_state->m_region_model->get_rvalue (var, nullptr);
+ const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
+
+ state_machine::state_t current
+ = old_smap->get_state (var_old_sval, m_ext_state);
+
+ return current;
+ }
+
+ state_machine::state_t get_state (const svalue *sval) final override
+ {
+ const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
+ state_machine::state_t current = old_smap->get_state (sval, m_ext_state);
+ return current;
+ }
+
+ void set_next_state (tree var,
+ state_machine::state_t to,
+ tree origin ATTRIBUTE_UNUSED) final override
+ {
+ state_machine::state_t from = get_state (var);
+ if (from != m_sm.get_start_state ())
+ return;
+ if (!is_transition_to_null (to))
+ return;
+
+ const svalue *var_new_sval
+ = m_new_state->m_region_model->get_rvalue (var, nullptr);
+
+ m_emission_path->add_event
+ (std::make_unique<state_change_event> (event_loc_info (*m_point),
+ m_stmt,
+ m_sm,
+ var_new_sval,
+ from, to,
+ nullptr,
+ *m_new_state,
+ nullptr));
+ }
+
+ void set_next_state (const svalue *sval,
+ state_machine::state_t to,
+ tree origin ATTRIBUTE_UNUSED) final override
+ {
+ state_machine::state_t from = get_state (sval);
+ if (from != m_sm.get_start_state ())
+ return;
+ if (!is_transition_to_null (to))
+ return;
+
+ m_emission_path->add_event
+ (std::make_unique<state_change_event> (event_loc_info (*m_point),
+ m_stmt,
+ m_sm,
+ sval,
+ from, to,
+ nullptr,
+ *m_new_state,
+ nullptr));
+ }
+
+ void warn (tree, std::unique_ptr<pending_diagnostic>) final override
+ {
+ }
+ void warn (const svalue *, std::unique_ptr<pending_diagnostic>) final override
+ {
+ }
+
+ tree get_diagnostic_tree (tree expr) final override
+ {
+ return expr;
+ }
+
+ tree get_diagnostic_tree (const svalue *sval) final override
+ {
+ return m_new_state->m_region_model->get_representative_tree (sval);
+ }
+
+ state_machine::state_t get_global_state () const final override
+ {
+ return 0;
+ }
+
+ void set_global_state (state_machine::state_t) final override
+ {
+ /* No-op. */
+ }
+
+ void clear_all_per_svalue_state () final override
+ {
+ /* No-op. */
+ }
+
+ void on_custom_transition (custom_transition *) final override
+ {
+ }
+
+ tree is_zero_assignment (const gimple *stmt) final override
+ {
+ const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+ if (!assign_stmt)
+ return NULL_TREE;
+ if (const svalue *sval
+ = m_new_state->m_region_model->get_gassign_result (assign_stmt, nullptr))
+ if (tree cst = sval->maybe_get_constant ())
+ if (::zerop(cst))
+ return gimple_assign_lhs (assign_stmt);
+ return NULL_TREE;
+ }
+
+ const program_state *get_old_program_state () const final override
+ {
+ return m_old_state;
+ }
+ const program_state *get_new_program_state () const final override
+ {
+ return m_new_state;
+ }
+
+ location_t get_emission_location () const final override
+ {
+ return UNKNOWN_LOCATION;
+ }
+
+ /* We only care about transitions to the "null" state
+ within sm-malloc. Special-case this. */
+ static bool is_transition_to_null (state_machine::state_t s)
+ {
+ return !strcmp (s->get_name (), "null");
+ }
+
+ const program_state *m_old_state;
+ const program_state *m_new_state;
+ const gimple *m_stmt;
+ const program_point *m_point;
+ checker_path *m_emission_path;
+ const extrinsic_state &m_ext_state;
+};
+
+void
+gimple_stmt_op::add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const
+{
+ out_path.add_event
+ (std::make_unique<statement_event> (&get_stmt (),
+ eedge.m_dest->get_function ()->decl,
+ eedge.m_dest->get_stack_depth (),
+ eedge.m_dest->get_state ()));
+
+ /* Create state change events for assignment to NULL.
+ Iterate through the stmts in dst_enode, adding state change
+ events for them. */
+ if (const gassign *assign = dyn_cast<const gassign *> (&m_stmt))
+ {
+ const program_point &src_point = eedge.m_src->get_point ();
+ const extrinsic_state &ext_state = out_path.get_ext_state ();
+ for (unsigned i = 0; i < ext_state.get_num_checkers (); i++)
+ {
+ const state_machine &sm = ext_state.get_sm (i);
+ null_assignment_sm_context sm_ctxt (i, sm,
+ &eedge.m_src->get_state (),
+ &eedge.m_dest->get_state (),
+ assign,
+ &src_point,
+ &out_path,
+ ext_state);
+ sm.on_stmt (sm_ctxt, assign);
+ // TODO: what about phi nodes?
+ }
+ }
+}
+
+// class gassign_op : public gimple_stmt_op
+
+// class greturn_op : public gimple_stmt_op
+
+void
+greturn_op::execute (operation_context &op_ctxt) const
+{
+ auto logger = op_ctxt.get_logger ();
+
+ auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+ const program_state &old_state = op_ctxt.get_initial_state ();
+ program_state dst_state (old_state);
+
+ impl_path_context path_ctxt (&dst_state, logger);
+ uncertainty_t uncertainty;
+ impl_region_model_context ctxt (op_ctxt.m_eg,
+ &op_ctxt.m_src_enode,
+
+ /* TODO: should we be getting the ECs from the
+ old state, rather than the new? */
+ &op_ctxt.get_initial_state (),
+ &dst_state,
+ &uncertainty,
+ &path_ctxt,
+ nullptr,
+ nullptr);
+
+ tree callee = op_ctxt.get_initial_point ().get_function ()->decl;
+ tree lhs = DECL_RESULT (callee);
+
+ if (lhs && get_retval ())
+ {
+ region_model *dst_region_model = dst_state.m_region_model;
+ const svalue *sval
+ = dst_region_model->get_rvalue (get_retval (), &ctxt);
+ const region *ret_reg = dst_region_model->get_lvalue (lhs, &ctxt);
+ dst_region_model->set_value (ret_reg, sval, &ctxt);
+ }
+
+ if (!path_ctxt.terminate_path_p ())
+ op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty);
+}
+
+bool
+greturn_op::
+execute_for_feasibility (const exploded_edge &eedge,
+ feasibility_state &fstate,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *) const
+{
+ tree callee = eedge.m_src->get_function ()->decl;
+ tree lhs = DECL_RESULT (callee);
+
+ if (lhs && get_retval ())
+ {
+ region_model &model = fstate.get_model ();
+ const svalue *sval = model.get_rvalue (get_retval (), ctxt);
+ const region *ret_reg = model.get_lvalue (lhs, ctxt);
+ model.set_value (ret_reg, sval, ctxt);
+ }
+
+ return true;
+}
+
+void
+greturn_op::add_any_events_for_eedge (const exploded_edge &,
+ checker_path &) const
+{
+ // No-op.
+}
+
+// class call_and_return_op : public gimple_stmt_op
+
+std::unique_ptr<operation>
+call_and_return_op::make (const gcall &call_stmt)
+{
+ if (is_special_named_call_p (call_stmt, "__analyzer_dump", 0))
+ return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state);
+ else if (is_special_named_call_p (call_stmt, "__analyzer_dump_sarif", 0))
+ return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::sarif);
+ else if (is_special_named_call_p (call_stmt, "__analyzer_dump_dot", 0))
+ return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::dot);
+ else if (is_special_named_call_p (call_stmt, "__analyzer_dump_state", 2))
+ return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state_2);
+ else if (is_setjmp_call_p (call_stmt))
+ return std::make_unique<setjmp_op> (call_stmt);
+ else if (is_longjmp_call_p (call_stmt))
+ return std::make_unique<longjmp_op> (call_stmt);
+ else if (is_cxa_throw_p (call_stmt))
+ return std::make_unique<cxa_throw_op> (call_stmt, false);
+ else if (is_cxa_rethrow_p (call_stmt))
+ return std::make_unique<cxa_throw_op> (call_stmt, true);
+
+ return std::make_unique<call_and_return_op> (call_stmt);
+}
+
+/* Resolve a function call by one of:
+
+ (a) using a call summary to add eedges to new enodes capturing
+ the states after summarized outcomes of the call
+
+ (b) adding an interprocedural_call edge, effectively "stepping into"
+ the called function, for detailed analysis of that path
+
+ (c) simulating the effect of the call, adding an eedge to a new
+ enode for the outcome of the call. */
+
+void
+call_and_return_op::execute (operation_context &op_ctxt) const
+{
+ /* Can we turn this into an interprocedural call, and execute within
+ the called fuction? */
+ const program_state &old_state = op_ctxt.get_initial_state ();
+ program_state dst_state (old_state);
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ ctxt.m_stmt = &get_gcall ();
+ call_details cd (get_gcall (), old_state.m_region_model, &ctxt);
+
+ /* Regardless of how we handle the call, check any known
+ preconditions. */
+ {
+ /* Check for any preconditions if it's a known_function. */
+ if (auto kf = maybe_get_known_function (cd))
+ kf->check_any_preconditions (cd);
+
+ /* Check for any preconditions using sm-state. */
+ {
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm
+ = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx);
+ const sm_state_map *old_smap
+ = old_state.m_checker_states[sm_idx];
+ sm_state_map *new_smap = dst_state.m_checker_states[sm_idx];
+ impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm,
+ &op_ctxt.m_src_enode,
+ &old_state, &dst_state,
+ old_smap, new_smap, nullptr);
+ sm.check_call_preconditions (sm_ctxt, cd);
+ }
+ }
+ }
+
+ if (tree callee_fndecl = cd.get_fndecl_for_call ())
+ {
+ // Consider using a call summary
+ if (function *called_fn = DECL_STRUCT_FUNCTION (callee_fndecl))
+ if (cgraph_edge *edge = get_any_cgraph_edge (op_ctxt))
+ if (op_ctxt.m_eg.get_analysis_plan ().use_summary_p (edge))
+ {
+ per_function_data *called_fn_data
+ = op_ctxt.m_eg.get_per_function_data (called_fn);
+ if (called_fn_data)
+ {
+ replay_call_summaries (op_ctxt, *called_fn,
+ *called_fn_data, &ctxt);
+ return;
+ }
+ }
+
+ // Do we have an entry snode for this fndecl?
+ if (auto callee_fun = DECL_STRUCT_FUNCTION (callee_fndecl))
+ if (supernode *callee_entry_snode
+ = (op_ctxt.get_supergraph ()
+ .get_node_for_function_entry (*callee_fun)))
+ {
+ const call_string *dst_call_string
+ (op_ctxt.m_src_enode
+ .get_point ()
+ .get_call_string ()
+ .push_call (op_ctxt.m_sedge, *this, *callee_fun));
+ const program_point dst_point
+ (callee_entry_snode, *dst_call_string);
+ auto edge_info
+ = std::make_unique<interprocedural_call> (get_gcall (),
+ *callee_fun);
+ edge_info->update_state (&dst_state, nullptr, &ctxt);
+ op_ctxt.add_outcome (dst_point, dst_state, false, nullptr,
+ std::move (edge_info));
+ return;
+ }
+ }
+
+ /* Resolve intraprocedurally: execute the gcall, but using the
+ dst_state from above so that any preconditions have been applied. */
+ gimple_stmt_op::execute_on_state (op_ctxt, std::move (dst_state));
+}
+
+cgraph_edge *
+call_and_return_op::get_any_cgraph_edge (operation_context &op_ctxt) const
+{
+ tree caller_fndecl = op_ctxt.get_initial_point ().get_fndecl ();
+ gcc_assert (caller_fndecl);
+
+ auto caller_cgnode = cgraph_node::get (caller_fndecl);
+ gcc_assert (caller_cgnode);
+ return caller_cgnode->get_edge (const_cast<gcall *> (&get_gcall ()));
+}
+
+void
+call_and_return_op::
+add_any_events_for_eedge (const exploded_edge &,
+ checker_path &) const
+{
+}
+
+/* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it
+ to *OUT if OUT is non-NULL), and return the corresponding argument
+ at the callsite. */
+
+tree
+call_and_return_op::get_arg_for_parm (tree callee_fndecl,
+ tree parm_to_find,
+ callsite_expr *out) const
+{
+ gcc_assert (TREE_CODE (parm_to_find) == PARM_DECL);
+
+ const gcall &call_stmt = get_gcall ();
+
+ unsigned i = 0;
+ for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
+ iter_parm = DECL_CHAIN (iter_parm), ++i)
+ {
+ if (i >= gimple_call_num_args (&call_stmt))
+ return NULL_TREE;
+ if (iter_parm == parm_to_find)
+ {
+ if (out)
+ *out = callsite_expr::from_zero_based_param (i);
+ return gimple_call_arg (&call_stmt, i);
+ }
+ }
+
+ /* Not found. */
+ return NULL_TREE;
+}
+
+/* Look for a use of ARG_TO_FIND as an argument at this callsite.
+ If found, return the default SSA def of the corresponding parm within
+ the callee, and if OUT is non-NULL, write the index to *OUT.
+ Only the first match is handled. */
+
+tree
+call_and_return_op::get_parm_for_arg (tree callee_fndecl,
+ tree arg_to_find,
+ callsite_expr *out) const
+{
+ const gcall &call_stmt = get_gcall ();
+
+ unsigned i = 0;
+ for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
+ iter_parm = DECL_CHAIN (iter_parm), ++i)
+ {
+ if (i >= gimple_call_num_args (&call_stmt))
+ return NULL_TREE;
+ tree param = gimple_call_arg (&call_stmt, i);
+ if (arg_to_find == param)
+ {
+ if (out)
+ *out = callsite_expr::from_zero_based_param (i);
+ return ssa_default_def (DECL_STRUCT_FUNCTION (callee_fndecl),
+ iter_parm);
+ }
+ }
+
+ /* Not found. */
+ return NULL_TREE;
+}
+
+/* Map caller_expr back to an expr within the callee, or return NULL_TREE.
+ If non-NULL is returned, populate OUT. */
+
+tree
+call_and_return_op::map_expr_from_caller_to_callee (tree callee_fndecl,
+ tree caller_expr,
+ callsite_expr *out) const
+{
+ /* Is it an argument (actual param)? If so, convert to
+ parameter (formal param). */
+ tree parm = get_parm_for_arg (callee_fndecl, caller_expr, out);
+ if (parm)
+ return parm;
+ /* Otherwise try return value. */
+ if (caller_expr == gimple_call_lhs (&get_gcall ()))
+ {
+ if (out)
+ *out = callsite_expr::from_return_value ();
+ return DECL_RESULT (callee_fndecl);
+ }
+
+ return NULL_TREE;
+}
+
+/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
+ If non-NULL is returned, populate OUT. */
+
+tree
+call_and_return_op::map_expr_from_callee_to_caller (tree callee_fndecl,
+ tree callee_expr,
+ callsite_expr *out) const
+{
+ if (callee_expr == NULL_TREE)
+ return NULL_TREE;
+
+ /* If it's a parameter (formal param), get the argument (actual param). */
+ if (TREE_CODE (callee_expr) == PARM_DECL)
+ return get_arg_for_parm (callee_fndecl, callee_expr, out);
+
+ /* Similar for the default SSA name of the PARM_DECL. */
+ if (TREE_CODE (callee_expr) == SSA_NAME
+ && SSA_NAME_IS_DEFAULT_DEF (callee_expr)
+ && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
+ return get_arg_for_parm (callee_fndecl, SSA_NAME_VAR (callee_expr), out);
+
+ /* Otherwise try return value. */
+ if (callee_expr == DECL_RESULT (callee_fndecl))
+ {
+ if (out)
+ *out = callsite_expr::from_return_value ();
+ return gimple_call_lhs (&get_gcall ());
+ }
+
+ return NULL_TREE;
+}
+
+const known_function *
+call_and_return_op::maybe_get_known_function (const call_details &cd) const
+{
+ region_model_manager *mgr = cd.get_manager ();
+ known_function_manager *known_fn_mgr = mgr->get_known_function_manager ();
+
+ if (gimple_call_internal_p (&get_gcall ()))
+ return known_fn_mgr->get_internal_fn
+ (gimple_call_internal_fn (&get_gcall ()));
+
+ if (tree callee_fndecl = cd.get_fndecl_for_call ())
+ return known_fn_mgr->get_match (callee_fndecl, cd);
+
+ return nullptr;
+}
+
+void
+call_and_return_op::
+replay_call_summaries (operation_context &op_ctxt,
+ function &called_fn,
+ per_function_data &called_fn_data,
+ region_model_context *ctxt) const
+{
+ logger *logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+
+ for (auto summary : called_fn_data.m_summaries)
+ {
+ gcc_assert (summary);
+ replay_call_summary (op_ctxt, called_fn, *summary, ctxt);
+ }
+}
+
+/* A concrete call_info subclass representing a replay of a call summary. */
+
+class call_summary_edge_info : public call_info
+{
+public:
+ call_summary_edge_info (const call_details &cd,
+ const function &called_fn,
+ call_summary &summary,
+ const extrinsic_state &ext_state)
+ : call_info (cd, called_fn),
+ m_called_fn (called_fn),
+ m_summary (summary),
+ m_ext_state (ext_state)
+ {}
+
+ bool update_state (program_state *state,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ /* Update STATE based on summary_end_state. */
+ call_details cd (get_call_details (state->m_region_model, ctxt));
+ call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
+ const program_state &summary_end_state = m_summary.get_state ();
+ return state->replay_call_summary (r, summary_end_state);
+ }
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ /* Update STATE based on summary_end_state. */
+ call_details cd (get_call_details (model, ctxt));
+ call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
+ const program_state &summary_end_state = m_summary.get_state ();
+ model->replay_call_summary (r, *summary_end_state.m_region_model);
+ return true;
+ }
+
+ void print_desc (pretty_printer &pp) const final override
+ {
+ pp_string (&pp, m_summary.get_desc ().get ());
+ }
+
+private:
+ const function &m_called_fn;
+ call_summary &m_summary;
+ const extrinsic_state &m_ext_state;
+};
+
+void
+call_and_return_op::
+replay_call_summary (operation_context &op_ctxt,
+ function &called_fn,
+ call_summary &summary,
+ region_model_context *ctxt) const
+{
+ logger *logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+ if (logger)
+ logger->log ("using %s as summary for call to %qE from %qE",
+ summary.get_desc ().get (),
+ called_fn.decl,
+ op_ctxt.get_initial_point ().get_function ()->decl);
+ const extrinsic_state &ext_state = op_ctxt.get_ext_state ();
+ const program_state &old_state = op_ctxt.get_initial_state ();
+ const program_state &summary_end_state = summary.get_state ();
+ if (logger)
+ {
+ pretty_printer *pp = logger->get_printer ();
+
+ logger->start_log_line ();
+ pp_string (pp, "callsite state: ");
+ old_state.dump_to_pp (ext_state, true, false, pp);
+ logger->end_log_line ();
+
+ logger->start_log_line ();
+ pp_string (pp, "summary end state: ");
+ summary_end_state.dump_to_pp (ext_state, true, false, pp);
+ logger->end_log_line ();
+ }
+
+ program_state new_state (old_state);
+
+ call_details cd (get_gcall (), new_state.m_region_model, ctxt);
+ call_summary_replay r (cd, called_fn, summary, ext_state);
+
+ if (new_state.replay_call_summary (r, summary_end_state))
+ op_ctxt.add_outcome
+ (op_ctxt.get_next_intraprocedural_point (),
+ new_state,
+ true,
+ nullptr,
+ std::make_unique<call_summary_edge_info> (cd,
+ called_fn,
+ summary,
+ ext_state));
+}
+
+// class dump_op : public call_and_return_op
+
+void
+dump_op::execute (operation_context &op_ctxt) const
+{
+ const program_state &state = op_ctxt.get_initial_state ();
+ switch (m_dump_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case dump_kind::state:
+ /* Handle the builtin "__analyzer_dump" by dumping state
+ to stderr. */
+ state.dump (op_ctxt.get_ext_state (), true);
+ break;
+ case dump_kind::sarif:
+ state.dump_sarif (op_ctxt.get_ext_state ());
+ break;
+ case dump_kind::dot:
+ state.dump_dot (op_ctxt.get_ext_state ());
+ break;
+ case dump_kind::state_2:
+ {
+ program_state dst_state (state);
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ dst_state.impl_call_analyzer_dump_state (get_gcall (),
+ op_ctxt.get_ext_state (),
+ &ctxt);
+ }
+ break;
+ }
+
+ op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (),
+ state, false, nullptr);
+}
+
+// class setjmp_op : public call_and_return_op
+
+void
+setjmp_op::execute (operation_context &op_ctxt) const
+{
+ program_state dst_state (op_ctxt.get_initial_state ());
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ dst_state.m_region_model->on_setjmp (get_gcall (),
+ op_ctxt.m_src_enode,
+ op_ctxt.m_sedge,
+ &ctxt);
+ op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (),
+ dst_state, true, nullptr);
+}
+
+void
+setjmp_op::add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const
+{
+ out_path.add_event
+ (std::make_unique<setjmp_event>
+ (event_loc_info (eedge.m_src),
+ eedge.m_src,
+ get_gcall ()));
+}
+
+// class longjmp_op : public call_and_return_op
+
+void
+longjmp_op::execute (operation_context &op_ctxt) const
+{
+ program_state dst_state (op_ctxt.get_initial_state ());
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ op_ctxt.m_src_enode.on_longjmp (op_ctxt.m_eg, get_gcall (), &dst_state,
+ &ctxt);
+}
+
+// class cxa_throw_op : public call_and_return_op
+
+void
+cxa_throw_op::execute (operation_context &op_ctxt) const
+{
+ program_state dst_state (op_ctxt.get_initial_state ());
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ program_point after_throw_point (op_ctxt.get_next_intraprocedural_point ());
+ op_ctxt.m_src_enode.on_throw (op_ctxt.m_eg,
+ get_gcall (),
+ after_throw_point,
+ &dst_state,
+ m_is_rethrow,
+ &ctxt);
+ // We don't continue along op_ctxt's superedge
+}
+
+// class control_flow_op : public operation
+
+void
+control_flow_op::
+walk_load_store_addr_ops (void *data,
+ walk_stmt_load_store_addr_fn load_cb,
+ walk_stmt_load_store_addr_fn store_cb,
+ walk_stmt_load_store_addr_fn addr_cb) const
+{
+ walk_stmt_load_store_addr_ops (const_cast <gimple *> (&m_ctrlflow_stmt),
+ data,
+ load_cb, store_cb, addr_cb);
+}
+
+void
+control_flow_op::add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const
+{
+ out_path.add_event
+ (std::make_unique<start_cfg_edge_event> (eedge,
+ event_loc_info (eedge.m_src),
+ this));
+ out_path.add_event
+ (std::make_unique<end_cfg_edge_event> (eedge,
+ event_loc_info (eedge.m_dest),
+ this));
+}
+
+/* Attempt to generate a description of any condition that holds at this edge.
+
+ The intent is to make the user-facing messages more clear, especially for
+ cases where there's a single or double-negative, such as
+ when describing the false branch of an inverted condition.
+
+ For example, rather than printing just:
+
+ | if (!ptr)
+ | ~
+ | |
+ | (1) following 'false' branch...
+
+ it's clearer to spell out the condition that holds:
+
+ | if (!ptr)
+ | ~
+ | |
+ | (1) following 'false' branch (when 'ptr' is non-NULL)...
+ ^^^^^^^^^^^^^^^^^^^^^^
+
+ In the above example, this function would generate the highlighted
+ string: "when 'ptr' is non-NULL".
+
+ If the edge is not a condition, or it's not clear that a description of
+ the condition would be helpful to the user, return NULL. */
+
+label_text
+control_flow_op::maybe_describe_condition (bool ) const
+{
+ return label_text::borrow (nullptr);
+}
+
+void
+control_flow_op::execute (operation_context &op_ctxt) const
+{
+ auto logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+
+ program_state dst_state (op_ctxt.get_initial_state ());
+ op_region_model_context ctxt (op_ctxt, dst_state);
+ if (apply_constraints (&op_ctxt.m_sedge,
+ *dst_state.m_region_model,
+ &ctxt,
+ nullptr))
+ {
+ bool unknown_side_effects;
+ handle_on_stmt_for_state_machines (op_ctxt,
+ dst_state,
+ nullptr,
+ unknown_side_effects,
+ m_ctrlflow_stmt);
+
+ if (!ctxt.terminate_path_p ())
+ {
+ auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+ op_ctxt.add_outcome (dst_point, dst_state, false, nullptr);
+ }
+ }
+}
+
+bool
+control_flow_op::
+execute_for_feasibility (const exploded_edge &eedge,
+ feasibility_state &fstate,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out_rc) const
+{
+ gcc_assert (eedge.m_sedge);
+ return apply_constraints (eedge.m_sedge,
+ fstate.get_model (),
+ ctxt,
+ out_rc);
+}
+
+// class gcond_edge_op : public control_flow_op
+
+gcond_edge_op::gcond_edge_op (::edge cfg_edge,
+ const gcond &cond_stmt)
+: control_flow_op (kind::cond_edge, cfg_edge, cond_stmt),
+ m_true_value (get_flags () & EDGE_TRUE_VALUE)
+{
+ /* Exactly one of EDGE_TRUE_VALUE and EDGE_FALSE_VALUE must
+ be set on CFG_EDGE. */
+ gcc_assert (static_cast<bool> (get_flags () & EDGE_TRUE_VALUE)
+ ^ static_cast<bool> (get_flags () & EDGE_FALSE_VALUE));
+}
+
+void
+gcond_edge_op::print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0);
+
+ if (m_true_value)
+ pp_printf (pp, "true");
+ else
+ pp_printf (pp, "false");
+}
+
+label_text
+gcond_edge_op::maybe_describe_condition (bool can_colorize) const
+{
+ const gcond &cond_stmt = get_gcond ();
+ enum tree_code op = gimple_cond_code (&cond_stmt);
+ tree lhs = gimple_cond_lhs (&cond_stmt);
+ tree rhs = gimple_cond_rhs (&cond_stmt);
+ if (!m_true_value)
+ op = invert_tree_comparison (op, false /* honor_nans */);
+ return maybe_describe_condition (can_colorize,
+ lhs, op, rhs);
+}
+
+/* Subroutine of gcond_edge_op::maybe_describe_condition above.
+
+ Attempt to generate a user-facing description of the condition
+ LHS OP RHS, but only if it is likely to make it easier for the
+ user to understand a condition. */
+
+label_text
+gcond_edge_op::maybe_describe_condition (bool can_colorize,
+ tree lhs,
+ enum tree_code op,
+ tree rhs)
+{
+ /* In theory we could just build a tree via
+ fold_build2 (op, boolean_type_node, lhs, rhs)
+ and print it with %qE on it, but this leads to warts such as
+ parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'. */
+
+ /* Special-case: describe testing the result of strcmp, as figuring
+ out what the "true" or "false" path is can be confusing to the user. */
+ if (TREE_CODE (lhs) == SSA_NAME
+ && zerop (rhs))
+ {
+ if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs)))
+ if (is_special_named_call_p (*call, "strcmp", 2))
+ {
+ if (op == EQ_EXPR)
+ return label_text::borrow ("when the strings are equal");
+ if (op == NE_EXPR)
+ return label_text::borrow ("when the strings are non-equal");
+ }
+ }
+
+ /* Only attempt to generate text for sufficiently simple expressions. */
+ if (!should_print_expr_p (lhs))
+ return label_text::borrow (nullptr);
+ if (!should_print_expr_p (rhs))
+ return label_text::borrow (nullptr);
+
+ /* Special cases for pointer comparisons against NULL. */
+ if (POINTER_TYPE_P (TREE_TYPE (lhs))
+ && POINTER_TYPE_P (TREE_TYPE (rhs))
+ && zerop (rhs))
+ {
+ if (op == EQ_EXPR)
+ return make_label_text (can_colorize, "when %qE is NULL",
+ lhs);
+ if (op == NE_EXPR)
+ return make_label_text (can_colorize, "when %qE is non-NULL",
+ lhs);
+ }
+
+ return make_label_text (can_colorize, "when %<%E %s %E%>",
+ lhs, op_symbol_code (op), rhs);
+}
+
+/* Subroutine of maybe_describe_condition.
+
+ Return true if EXPR is we will get suitable user-facing output
+ from %E on it. */
+
+bool
+gcond_edge_op::should_print_expr_p (tree expr)
+{
+ if (TREE_CODE (expr) == SSA_NAME)
+ {
+ if (SSA_NAME_VAR (expr))
+ return should_print_expr_p (SSA_NAME_VAR (expr));
+ else
+ return false;
+ }
+
+ if (DECL_P (expr))
+ return true;
+
+ if (CONSTANT_CLASS_P (expr))
+ return true;
+
+ return false;
+}
+
+bool
+gcond_edge_op::
+apply_constraints (const superedge *,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ const gcond &cond_stmt = get_gcond ();
+ enum tree_code op = gimple_cond_code (&cond_stmt);
+ tree lhs = gimple_cond_lhs (&cond_stmt);
+ tree rhs = gimple_cond_rhs (&cond_stmt);
+ if (!m_true_value)
+ op = invert_tree_comparison (op, false /* honor_nans */);
+ return model.add_constraint (lhs, op, rhs, ctxt, out);
+}
+
+// class ggoto_edge_op : public control_flow_op
+
+ggoto_edge_op::ggoto_edge_op (::edge cfg_edge,
+ const ggoto &goto_stmt,
+ tree dst_label)
+: control_flow_op (kind::goto_edge, cfg_edge, goto_stmt),
+ m_dst_label (dst_label)
+{
+}
+
+void
+ggoto_edge_op::print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0);
+
+ if (m_dst_label)
+ pp_printf (pp, "%qD", m_dst_label);
+}
+
+label_text
+ggoto_edge_op::maybe_describe_condition (bool) const
+{
+ return label_text::borrow ("");
+}
+
+bool
+ggoto_edge_op::
+apply_constraints (const superedge *,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+ const ggoto &goto_stmt = get_ggoto ();
+ tree dest = gimple_goto_dest (&goto_stmt);
+ const svalue *dest_sval = model.get_rvalue (dest, ctxt);
+
+ /* If we know we were jumping to a specific label. */
+ if (m_dst_label)
+ {
+ auto mgr = model.get_manager ();
+ const label_region *dst_label_reg
+ = mgr->get_region_for_label (m_dst_label);
+ const svalue *dst_label_ptr
+ = mgr->get_ptr_svalue (ptr_type_node, dst_label_reg);
+
+ if (!model.add_constraint (dest_sval, EQ_EXPR, dst_label_ptr, ctxt))
+ return false;
+ }
+
+ return true;
+}
+
+// class switch_case_op : public control_flow_op
+
+switch_case_op::switch_case_op (function &fun,
+ ::edge cfg_edge,
+ const gswitch &switch_stmt,
+ bounded_ranges_manager &mgr)
+: control_flow_op (kind::switch_edge, cfg_edge, switch_stmt)
+{
+ /* Populate m_case_labels with all cases which go to DST. */
+ for (unsigned i = 0; i < gimple_switch_num_labels (&switch_stmt); i++)
+ {
+ tree case_ = gimple_switch_label (&switch_stmt, i);
+ basic_block bb = label_to_block (&fun,
+ CASE_LABEL (case_));
+ if (bb == cfg_edge->dest)
+ m_case_labels.push_back (case_);
+ }
+
+ auto_vec <const bounded_ranges *> case_ranges_vec
+ (gimple_switch_num_labels (&switch_stmt));
+ for (auto case_label : m_case_labels)
+ {
+ /* Get the ranges for this case label. */
+ const bounded_ranges *case_ranges
+ = mgr.make_case_label_ranges (&switch_stmt, case_label);
+ case_ranges_vec.quick_push (case_ranges);
+ }
+
+ m_all_cases_ranges = mgr.get_or_create_union (case_ranges_vec);
+}
+
+/* Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP. */
+
+void
+switch_case_op::print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (user_facing)
+ {
+ for (unsigned i = 0; i < m_case_labels.size (); ++i)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ tree case_label = m_case_labels[i];
+ gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+ tree lower_bound = CASE_LOW (case_label);
+ tree upper_bound = CASE_HIGH (case_label);
+ if (lower_bound)
+ {
+ pp_printf (pp, "case ");
+ dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+ if (upper_bound)
+ {
+ pp_printf (pp, " ... ");
+ dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
+ false);
+ }
+ pp_printf (pp, ":");
+ }
+ else
+ pp_printf (pp, "default:");
+ }
+ }
+ else
+ {
+ pp_character (pp, '{');
+ for (unsigned i = 0; i < m_case_labels.size (); ++i)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ tree case_label = m_case_labels[i];
+ gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+ tree lower_bound = CASE_LOW (case_label);
+ tree upper_bound = CASE_HIGH (case_label);
+ if (lower_bound)
+ {
+ if (upper_bound)
+ {
+ pp_character (pp, '[');
+ dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0,
+ false);
+ pp_string (pp, ", ");
+ dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
+ false);
+ pp_character (pp, ']');
+ }
+ else
+ dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+ }
+ else
+ pp_printf (pp, "default");
+ }
+ pp_character (pp, '}');
+ if (implicitly_created_default_p ())
+ {
+ pp_string (pp, " IMPLICITLY CREATED");
+ }
+ }
+}
+
+/* Return true iff SWITCH_STMT has a non-default label that contains
+ INT_CST. */
+
+static bool
+has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst)
+{
+ /* We expect the initial label to be the default; skip it. */
+ gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL_TREE);
+ unsigned min_idx = 1;
+ unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1;
+
+ /* Binary search: try to find the label containing INT_CST.
+ This requires the cases to be sorted by CASE_LOW (done by the
+ gimplifier). */
+ while (max_idx >= min_idx)
+ {
+ unsigned case_idx = (min_idx + max_idx) / 2;
+ tree label = gimple_switch_label (switch_stmt, case_idx);
+ tree low = CASE_LOW (label);
+ gcc_assert (low);
+ tree high = CASE_HIGH (label);
+ if (!high)
+ high = low;
+ if (tree_int_cst_compare (int_cst, low) < 0)
+ {
+ /* INT_CST is below the range of this label. */
+ gcc_assert (case_idx > 0);
+ max_idx = case_idx - 1;
+ }
+ else if (tree_int_cst_compare (int_cst, high) > 0)
+ {
+ /* INT_CST is above the range of this case. */
+ min_idx = case_idx + 1;
+ }
+ else
+ /* This case contains INT_CST. */
+ return true;
+ }
+ /* Not found. */
+ return false;
+}
+
+/* Return true iff SWITCH_STMT (which must be on an enum value)
+ has nondefault cases handling all values in the enum. */
+
+static bool
+has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt,
+ tree type)
+{
+ gcc_assert (switch_stmt);
+ gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE);
+
+ for (tree enum_val_iter = TYPE_VALUES (type);
+ enum_val_iter;
+ enum_val_iter = TREE_CHAIN (enum_val_iter))
+ {
+ tree enum_val = TREE_VALUE (enum_val_iter);
+ gcc_assert (TREE_CODE (enum_val) == CONST_DECL);
+ gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST);
+ if (!has_nondefault_case_for_value_p (switch_stmt,
+ DECL_INITIAL (enum_val)))
+ return false;
+ }
+ return true;
+}
+
+/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
+ for the edge to be taken.
+
+ If they are feasible, add the constraints and return true.
+
+ Return false if the constraints contradict existing knowledge
+ (and so the edge should not be taken).
+ When returning false, if OUT is non-NULL, write a new rejected_constraint
+ to it. */
+
+bool
+switch_case_op::
+apply_constraints (const superedge *,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ const gswitch *switch_stmt = &get_gswitch ();
+ tree index = gimple_switch_index (switch_stmt);
+ const svalue *index_sval = model.get_rvalue (index, ctxt);
+ bool check_index_type = true;
+
+ /* With -fshort-enum, there may be a type cast. */
+ if (ctxt && index_sval->get_kind () == SK_UNARYOP
+ && TREE_CODE (index_sval->get_type ()) == INTEGER_TYPE)
+ {
+ const unaryop_svalue *unaryop = as_a <const unaryop_svalue *> (index_sval);
+ if (unaryop->get_op () == NOP_EXPR
+ && is_a <const initial_svalue *> (unaryop->get_arg ()))
+ if (const initial_svalue *initvalop = (as_a <const initial_svalue *>
+ (unaryop->get_arg ())))
+ if (initvalop->get_type ()
+ && TREE_CODE (initvalop->get_type ()) == ENUMERAL_TYPE)
+ {
+ index_sval = initvalop;
+ check_index_type = false;
+ }
+ }
+
+ /* If we're switching based on an enum type, assume that the user is only
+ working with values from the enum. Hence if this is an
+ implicitly-created "default", assume it doesn't get followed.
+ This fixes numerous "uninitialized" false positives where we otherwise
+ consider jumping past the initialization cases. */
+
+ if (/* Don't check during feasibility-checking (when ctxt is NULL). */
+ ctxt
+ /* Must be an enum value. */
+ && index_sval->get_type ()
+ && (!check_index_type
+ || TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE)
+ && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE
+ /* If we have a constant, then we can check it directly. */
+ && index_sval->get_kind () != SK_CONSTANT
+ && implicitly_created_default_p ()
+ && has_nondefault_cases_for_all_enum_values_p (switch_stmt,
+ index_sval->get_type ())
+ /* Don't do this if there's a chance that the index is
+ attacker-controlled. */
+ && !ctxt->possibly_tainted_p (index_sval))
+ {
+ if (out)
+ *out = std::make_unique <rejected_default_case> (model);
+ return false;
+ }
+
+ bool sat
+ = model.get_constraints ()->add_bounded_ranges (index_sval,
+ m_all_cases_ranges);
+ if (!sat && out)
+ *out = std::make_unique <rejected_ranges_constraint>
+ (model, index, m_all_cases_ranges);
+ if (sat && ctxt && !m_all_cases_ranges->empty_p ())
+ ctxt->on_bounded_ranges (*index_sval, *m_all_cases_ranges);
+ return sat;
+}
+
+/* Return true iff this op's edge is purely for an
+ implicitly-created "default". */
+
+bool
+switch_case_op::implicitly_created_default_p () const
+{
+ if (m_case_labels.size () != 1)
+ return false;
+
+ tree case_label = m_case_labels[0];
+ gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+ if (CASE_LOW (case_label))
+ return false;
+
+ /* We have a single "default" case.
+ Assume that it was implicitly created if it has UNKNOWN_LOCATION. */
+ return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION;
+}
+
+/* Given an ERT_TRY region, get the eh_catch corresponding to
+ the label of DST_SNODE, if any. */
+
+static eh_catch
+get_catch (eh_region eh_reg, supernode *dst_snode)
+{
+ gcc_assert (eh_reg->type == ERT_TRY);
+
+ tree dst_snode_label = dst_snode->get_label ();
+ if (!dst_snode_label)
+ return nullptr;
+
+ for (eh_catch iter = eh_reg->u.eh_try.first_catch;
+ iter;
+ iter = iter->next_catch)
+ if (iter->label == dst_snode_label)
+ return iter;
+
+ return nullptr;
+}
+
+class rejected_eh_dispatch : public rejected_constraint
+{
+public:
+ rejected_eh_dispatch (const region_model &model)
+ : rejected_constraint (model)
+ {}
+
+ void dump_to_pp (pretty_printer *pp) const final override
+ {
+ pp_printf (pp, "rejected_eh_dispatch");
+ }
+};
+
+static bool
+exception_matches_type_p (tree exception_type,
+ tree catch_type)
+{
+ if (catch_type == exception_type)
+ return true;
+
+ /* TODO (PR analyzer/119697): we should also handle subclasses etc;
+ see the rules in https://en.cppreference.com/w/cpp/language/catch
+
+ It looks like we should be calling (or emulating)
+ can_convert_eh from the C++ FE, but that's specific to the C++ FE. */
+
+ return false;
+}
+
+static bool
+matches_any_exception_type_p (eh_catch ehc, tree exception_type)
+{
+ if (ehc->type_list == NULL_TREE)
+ /* All exceptions are caught here. */
+ return true;
+
+ for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ return true;
+ return false;
+}
+
+// class eh_dispatch_edge_op : public control_flow_op
+
+std::unique_ptr<eh_dispatch_edge_op>
+eh_dispatch_edge_op::make (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge cfg_edge,
+ const geh_dispatch &eh_dispatch_stmt)
+{
+ const eh_status *eh = src_snode->get_function ()->eh;
+ gcc_assert (eh);
+ int region_idx = gimple_eh_dispatch_region (&eh_dispatch_stmt);
+ gcc_assert (region_idx > 0);
+ gcc_assert ((*eh->region_array)[region_idx]);
+ eh_region eh_reg = (*eh->region_array)[region_idx];
+ gcc_assert (eh_reg);
+ switch (eh_reg->type)
+ {
+ default:
+ gcc_unreachable ();
+ case ERT_CLEANUP:
+ // TODO
+ gcc_unreachable ();
+ break;
+ case ERT_TRY:
+ {
+ eh_catch ehc = get_catch (eh_reg, dst_snode);
+ return std::make_unique<eh_dispatch_try_edge_op>
+ (src_snode, dst_snode,
+ cfg_edge, eh_dispatch_stmt,
+ eh_reg, ehc);
+ }
+ break;
+ case ERT_ALLOWED_EXCEPTIONS:
+ return std::make_unique<eh_dispatch_allowed_edge_op>
+ (src_snode, dst_snode,
+ cfg_edge, eh_dispatch_stmt,
+ eh_reg);
+ break;
+ case ERT_MUST_NOT_THROW:
+ // TODO
+ gcc_unreachable ();
+ break;
+ }
+}
+
+eh_dispatch_edge_op::
+eh_dispatch_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ enum kind kind_,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg)
+: control_flow_op (kind_, cfg_edge, geh_dispatch_stmt),
+ m_src_snode (src_snode),
+ m_dst_snode (dst_snode),
+ m_eh_region (eh_reg)
+{
+}
+
+bool
+eh_dispatch_edge_op::
+apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ const exception_node *current_node = model.get_current_thrown_exception ();
+
+ if (!current_node)
+ return false;
+
+ gcc_assert (current_node);
+ tree curr_exception_type = current_node->maybe_get_type ();
+ if (!curr_exception_type)
+ /* We don't know the specific type. */
+ return true;
+
+ return apply_eh_constraints (sedge, model, ctxt, curr_exception_type, out);
+}
+
+// class eh_dispatch_try_edge_op : public eh_dispatch_edge_op
+
+eh_dispatch_try_edge_op::
+eh_dispatch_try_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg,
+ eh_catch ehc)
+: eh_dispatch_edge_op (src_snode, dst_snode,
+ kind::eh_dispatch_try_edge,
+ cfg_edge, geh_dispatch_stmt, eh_reg),
+ m_eh_catch (ehc)
+{
+ gcc_assert (eh_reg->type == ERT_TRY);
+}
+
+void
+eh_dispatch_try_edge_op::print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ pp_string (pp, "ERT_TRY: ");
+ if (m_eh_catch)
+ {
+ bool first = true;
+ for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter))
+ {
+ if (!first)
+ pp_string (pp, ", ");
+ pp_printf (pp, "on catch %qT", TREE_VALUE (iter));
+ first = false;
+ }
+ }
+ else
+ pp_string (pp, "on uncaught exception");
+}
+
+void
+eh_dispatch_try_edge_op::add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const
+{
+ if (m_eh_catch)
+ {
+ const region_model *model = eedge.m_src->get_state ().m_region_model;
+ auto curr_thrown_exception_node
+ = model->get_current_thrown_exception ();
+ gcc_assert (curr_thrown_exception_node);
+ tree type = curr_thrown_exception_node->maybe_get_type ();
+ out_path.add_event
+ (std::make_unique<catch_cfg_edge_event>
+ (eedge,
+ event_loc_info (eedge.m_dest),
+ *this,
+ type));
+ }
+ else
+ {
+ /* We have the "uncaught exception" sedge, from eh_dispatch
+ to a block containing resx.
+ Don't add any events for this, so that we can consolidate
+ adjacent stack unwinding events. */
+ }
+}
+
+bool
+eh_dispatch_try_edge_op::
+apply_eh_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context */*ctxt*/,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ /* TODO: can we rely on this ordering?
+ or do we need to iterate through prev_catch ? */
+ /* The exception must not match any of the previous edges. */
+ for (auto sibling_sedge : get_src_snode ()->m_succs)
+ {
+ if (sibling_sedge == sedge)
+ break;
+
+ const eh_dispatch_try_edge_op *sibling_edge_op
+ = (const eh_dispatch_try_edge_op *)sibling_sedge->get_op ();
+ if (eh_catch ehc = sibling_edge_op->m_eh_catch)
+ if (matches_any_exception_type_p (ehc, exception_type))
+ {
+ /* The earlier sibling matches, so the "unhandled" edge is
+ not taken. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (model);
+ return false;
+ }
+ }
+
+ if (eh_catch ehc = m_eh_catch)
+ {
+ /* We have an edge that tried to match one or more types. */
+
+ /* The exception must not match any of the previous edges. */
+
+ /* It must match this type. */
+ if (matches_any_exception_type_p (ehc, exception_type))
+ return true;
+ else
+ {
+ /* Exception type doesn't match. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (model);
+ return false;
+ }
+ }
+ else
+ {
+ /* This is the "unhandled exception" edge.
+ If we get here then no sibling edges matched;
+ we will follow this edge. */
+ return true;
+ }
+}
+
+// class eh_dispatch_allowed_edge_op : public eh_dispatch_edge_op
+
+eh_dispatch_allowed_edge_op::
+eh_dispatch_allowed_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg)
+: eh_dispatch_edge_op (src_snode, dst_snode,
+ kind::eh_dispatch_try_edge,
+ cfg_edge, geh_dispatch_stmt, eh_reg)
+{
+ gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS);
+
+ /* We expect two sibling out-edges at an eh_dispatch from such a region:
+
+ - one to a bb without a gimple label, with a resx,
+ for exceptions of expected types
+
+ - one to a bb with a gimple label, with a call to __cxa_unexpected,
+ for exceptions of unexpected types.
+
+ Set m_kind for this edge accordingly. */
+ gcc_assert (cfg_edge->src->succs->length () == 2);
+ tree label_for_unexpected_exceptions = eh_reg->u.allowed.label;
+ tree label_for_dest_enode = dst_snode->get_label ();
+ if (label_for_dest_enode == label_for_unexpected_exceptions)
+ m_kind = eh_kind::unexpected;
+ else
+ {
+ gcc_assert (label_for_dest_enode == nullptr);
+ m_kind = eh_kind::expected;
+ }
+}
+
+void
+eh_dispatch_allowed_edge_op::print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ {
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case eh_kind::expected:
+ pp_string (pp, "expected: ");
+ break;
+ case eh_kind::unexpected:
+ pp_string (pp, "unexpected: ");
+ break;
+ }
+ pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: ");
+ eh_region eh_reg = get_eh_region ();
+ bool first = true;
+ for (tree iter = eh_reg->u.allowed.type_list; iter;
+ iter = TREE_CHAIN (iter))
+ {
+ if (!first)
+ pp_string (pp, ", ");
+ pp_printf (pp, "%qT", TREE_VALUE (iter));
+ first = false;
+ }
+ }
+}
+
+bool
+eh_dispatch_allowed_edge_op::
+apply_eh_constraints (const superedge *,
+ region_model &model,
+ region_model_context */*ctxt*/,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ auto curr_thrown_exception_node = model.get_current_thrown_exception ();
+ gcc_assert (curr_thrown_exception_node);
+ tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
+ eh_region eh_reg = get_eh_region ();
+ tree type_list = eh_reg->u.allowed.type_list;
+
+ switch (get_eh_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case eh_kind::expected:
+ if (!curr_exception_type)
+ {
+ /* We don't know the specific type;
+ assume we have one of an expected type. */
+ return true;
+ }
+ for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ return true;
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (model);
+ return false;
+
+ case eh_kind::unexpected:
+ if (!curr_exception_type)
+ {
+ /* We don't know the specific type;
+ assume we don't have one of an expected type. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (model);
+ return false;
+ }
+ for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ {
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (model);
+ return false;
+ }
+ return true;
+ }
+}
+
+// class phis_for_edge_op : public operation
+
+std::unique_ptr<operation>
+phis_for_edge_op::maybe_make (::edge cfg_in_edge)
+{
+ std::vector<pair> pairs = get_pairs_for_phi_along_in_edge (cfg_in_edge);
+ if (pairs.empty ())
+ return nullptr;
+
+ return std::make_unique <phis_for_edge_op> (std::move (pairs),
+ cfg_in_edge);
+}
+
+phis_for_edge_op::phis_for_edge_op (std::vector<pair> &&pairs,
+ ::edge cfg_in_edge)
+: operation (kind::phis),
+ m_pairs (std::move (pairs)),
+ m_cfg_in_edge (cfg_in_edge)
+{
+}
+
+std::vector<phis_for_edge_op::pair>
+phis_for_edge_op::get_pairs_for_phi_along_in_edge (::edge cfg_in_edge)
+{
+ std::vector<pair> result;
+
+ const size_t phi_arg_idx = cfg_in_edge->dest_idx;
+ for (gphi_iterator gpi = gsi_start_phis (cfg_in_edge->dest);
+ !gsi_end_p (gpi); gsi_next (&gpi))
+ {
+ gphi * const phi = gpi.phi ();
+ tree dst = gimple_phi_result (phi);
+
+ /* We don't bother tracking the .MEM SSA names. */
+ if (tree var = SSA_NAME_VAR (dst))
+ if (TREE_CODE (var) == VAR_DECL)
+ if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+ continue;
+
+ tree src = gimple_phi_arg_def (phi, phi_arg_idx);
+
+ result.push_back ({dst, src});
+ }
+
+ return result;
+}
+
+void
+phis_for_edge_op::print_as_edge_label (pretty_printer *pp,
+ bool ) const
+{
+ pp_printf (pp, "PHI(");
+ bool first = true;
+ for (auto &p : m_pairs)
+ {
+ if (first)
+ first = false;
+ else
+ pp_string (pp, ", ");
+
+ pp_printf (pp, "%E = %E", p.m_dst, p.m_src);
+ }
+ pp_printf (pp, ");");
+}
+
+void
+phis_for_edge_op::
+walk_load_store_addr_ops (void */*data*/ ,
+ walk_stmt_load_store_addr_fn /*load_cb*/,
+ walk_stmt_load_store_addr_fn /*store_cb*/,
+ walk_stmt_load_store_addr_fn /*addr_cb*/) const
+{
+}
+
+bool
+phis_for_edge_op::defines_ssa_name_p (const_tree ssa_name) const
+{
+ for (auto &p : m_pairs)
+ if (p.m_dst == ssa_name)
+ return true;
+ return false;
+}
+
+void
+phis_for_edge_op::execute (operation_context &op_ctxt) const
+{
+ auto logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+
+ auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+
+ const program_state &src_state (op_ctxt.get_initial_state ());
+ program_state dst_state (src_state);
+
+ impl_path_context path_ctxt (&dst_state, logger);
+ uncertainty_t uncertainty;
+ impl_region_model_context ctxt (op_ctxt.m_eg,
+ &op_ctxt.m_src_enode,
+
+ /* TODO: should we be getting the ECs from the
+ old state, rather than the new? */
+ &op_ctxt.get_initial_state (),
+ &dst_state,
+ &uncertainty,
+ &path_ctxt,
+ nullptr,
+ nullptr);
+
+ update_state (src_state, dst_state, &ctxt);
+
+ op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty);
+}
+
+void
+phis_for_edge_op::update_state (const program_state &src_state,
+ program_state &dst_state,
+ region_model_context *ctxt) const
+{
+ const region_model &src_model = *src_state.m_region_model;
+ region_model &dst_model = *dst_state.m_region_model;
+
+ hash_set<const svalue *> svals_changing_meaning;
+
+ /* Get state from src_state so that all of the phi stmts for an edge
+ are effectively handled simultaneously. */
+ for (auto &p : m_pairs)
+ {
+ const svalue *src_sval = src_model.get_rvalue (p.m_src, nullptr);
+ const region *dst_reg = src_model.get_lvalue (p.m_dst, nullptr);
+
+ const svalue *old_sval = src_model.get_rvalue (p.m_dst, nullptr);
+ if (old_sval->get_kind () == SK_WIDENING)
+ svals_changing_meaning.add (old_sval);
+
+ dst_model.set_value (dst_reg, src_sval, ctxt);
+ }
+
+ for (auto iter : svals_changing_meaning)
+ dst_model.get_constraints ()->purge_state_involving (iter);
+}
+
+bool
+phis_for_edge_op::
+execute_for_feasibility (const exploded_edge &eedge,
+ feasibility_state &fstate,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+ hash_set<const svalue *> svals_changing_meaning;
+ /* Get state from src_state so that all of the phi stmts for an edge
+ are effectively handled simultaneously. */
+ region_model &model = fstate.get_model ();
+ region_model src_model (model);
+ for (auto &p : m_pairs)
+ {
+ const svalue *src_sval = src_model.get_rvalue (p.m_src, ctxt);
+ const region *dst_reg = model.get_lvalue (p.m_dst, ctxt);
+
+ const svalue *sval = model.get_rvalue (p.m_dst, ctxt);
+ if (sval->get_kind () == SK_WIDENING)
+ svals_changing_meaning.add (sval);
+
+ model.set_value (dst_reg, src_sval, ctxt);
+ }
+
+ for (auto iter : svals_changing_meaning)
+ model.get_constraints ()->purge_state_involving (iter);
+
+ {
+ /* If we've entering an snode that we've already visited on this
+ epath, then we need do fix things up for loops; see the
+ comment for store::loop_replay_fixup.
+ Perhaps we should probably also verify the callstring,
+ and track program_points, but hopefully doing it by supernode
+ is good enough. */
+ const exploded_node &dst_enode = *eedge.m_dest;
+ const unsigned dst_snode_idx = dst_enode.get_supernode ()->m_id;
+ if (bitmap_bit_p (fstate.get_snodes_visited (), dst_snode_idx))
+ model.loop_replay_fixup (dst_enode.get_state ().m_region_model);
+ }
+
+ return true;
+}
+
+void
+phis_for_edge_op::
+update_state_for_bulk_merger (const program_state &src_state,
+ program_state &dst_state) const
+{
+ update_state (src_state, dst_state, nullptr);
+}
+
+void
+phis_for_edge_op::add_any_events_for_eedge (const exploded_edge &,
+ checker_path &) const
+{
+ // No-op
+}
+
+// class resx_op : public gimple_stmt_op
+
+void
+resx_op::execute (operation_context &op_ctxt) const
+{
+ auto logger = op_ctxt.get_logger ();
+ LOG_SCOPE (logger);
+
+ program_point dst_point (op_ctxt.get_next_intraprocedural_point ());
+ program_state dst_state (op_ctxt.get_initial_state ());
+ op_region_model_context ctxt (op_ctxt, dst_state);
+
+ if (exploded_node *dst_enode
+ = op_ctxt.m_eg.get_or_create_node (dst_point, dst_state,
+ &op_ctxt.m_src_enode,
+ // Don't add to worklist:
+ false))
+ {
+ op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode,
+ dst_enode,
+ &op_ctxt.m_sedge,
+ false,
+ nullptr);
+ /* Try to adding eedges and enodes that unwind to the next
+ eh_dispatch statement, if any.
+ Only the final enode is added to the worklist. */
+ op_ctxt.m_eg.unwind_from_exception (*dst_enode,
+ nullptr,
+ &ctxt);
+ }
+}
+
+void
+resx_op::add_any_events_for_eedge (const exploded_edge &,
+ checker_path &) const
+{
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
--- /dev/null
+/* Operations within the code being analyzed.
+ 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_OPS_H
+#define GCC_ANALYZER_OPS_H
+
+#include "except.h"
+#include "gimple-walk.h"
+
+namespace ana {
+
+class operation;
+ class control_flow_op;
+ class call_and_return_op;
+ class phis_for_edge_op;
+
+class callsite_expr;
+
+struct operation_context
+{
+ operation_context (exploded_graph &eg,
+ exploded_node &src_enode,
+ const superedge &sedge)
+ : m_eg (eg),
+ m_src_enode (src_enode),
+ m_sedge (sedge)
+ {
+ }
+
+ void DEBUG_FUNCTION dump () const;
+
+ logger *get_logger () const;
+
+ const extrinsic_state &get_ext_state () const;
+
+ const program_point &
+ get_initial_point () const;
+
+ const program_state &
+ get_initial_state () const;
+
+ const supergraph &
+ get_supergraph () const;
+
+ program_point
+ get_next_intraprocedural_point () const;
+
+ void
+ add_outcome (const program_point &dst_point,
+ program_state dst_state,
+ bool could_do_work,
+ uncertainty_t *uncertainty,
+ std::unique_ptr<custom_edge_info> info = nullptr);
+
+ exploded_graph &m_eg;
+ exploded_node &m_src_enode;
+ const superedge &m_sedge;
+};
+
+/* Abstract base class for an operation along a superedge. */
+
+class operation
+{
+ public:
+ // Discriminator for concrete subclasses
+ enum kind
+ {
+ asm_stmt,
+ assignment,
+ predict_stmt,
+ return_stmt,
+ resx,
+ cond_edge,
+ goto_edge,
+ switch_edge,
+ eh_dispatch_try_edge,
+ eh_dispatch_allowed_edge,
+ phis,
+ call_and_return
+ };
+
+ ~operation () {}
+
+ void
+ dump () const;
+
+ virtual std::unique_ptr<operation>
+ clone () const = 0;
+
+ virtual void
+ print_as_edge_label (pretty_printer *pp, bool user_facing) const = 0;
+
+ virtual bool
+ defines_ssa_name_p (const_tree ssa_name) const = 0;
+
+ virtual void
+ walk_load_store_addr_ops (void *,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn) const = 0;
+
+ virtual const gimple *
+ maybe_get_stmt () const
+ {
+ return nullptr;
+ }
+
+ virtual void
+ execute (operation_context &op_ctxt) const = 0;
+
+ virtual bool
+ execute_for_feasibility (const exploded_edge &,
+ feasibility_state &,
+ region_model_context *,
+ std::unique_ptr<rejected_constraint> */*out_rc*/) const
+ {
+ // no-op
+ return true;
+ }
+
+ /* Is this op suitable for bulk-merging?
+ It must have a single outcome, at the intraprocedural
+ next point, with some state. */
+ virtual bool
+ supports_bulk_merge_p () const = 0;
+ virtual void
+ update_state_for_bulk_merger (const program_state &,
+ program_state &) const
+ {
+ /* Must be implemented for any subclasses that return true
+ for supports_bulk_merge_p. */
+ gcc_unreachable ();
+ }
+ virtual void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const = 0;
+
+ virtual const control_flow_op *
+ dyn_cast_control_flow_op () const { return nullptr; }
+
+ virtual const call_and_return_op *
+ dyn_cast_call_and_return_op () const { return nullptr; }
+
+ virtual const phis_for_edge_op *
+ dyn_cast_phis_for_edge_op () const { return nullptr; }
+
+ enum kind get_kind () const { return m_kind; }
+
+protected:
+ operation (enum kind kind_)
+ : m_kind (kind_)
+ {
+ }
+
+ static void
+ handle_on_stmt_for_state_machines (operation_context &op_ctxt,
+ program_state &dst_state,
+ path_context *path_ctxt,
+ bool &unknown_side_effects,
+ const gimple &stmt);
+
+private:
+ enum kind m_kind;
+};
+
+/* Subclass for an operation representing a specific gimple stmt
+ that isn't control flow. */
+
+class gimple_stmt_op : public operation
+{
+public:
+ const gimple &get_stmt () const { return m_stmt; }
+
+ void
+ print_as_edge_label (pretty_printer *pp, bool user_facing) const override;
+
+ bool
+ defines_ssa_name_p (const_tree ssa_name) const final override;
+
+ void
+ walk_load_store_addr_ops (void *,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn) const final override;
+
+ const gimple *
+ maybe_get_stmt () const final override
+ {
+ return &m_stmt;
+ }
+
+ void
+ execute (operation_context &op_ctxt) const override;
+
+ void
+ execute_on_state (operation_context &op_ctxt,
+ program_state dst_state) const;
+
+ bool
+ execute_for_feasibility (const exploded_edge &,
+ feasibility_state &,
+ region_model_context *,
+ std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+ virtual bool
+ supports_bulk_merge_p () const override;
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const override;
+
+protected:
+ gimple_stmt_op (enum kind kind_, const gimple &stmt)
+ : operation (kind_), m_stmt (stmt)
+ {}
+
+private:
+ const gimple &m_stmt;
+};
+
+/* Various subclasses of gimple_stmt_op. */
+
+/* An operation subclass representing the effect of a GIMPLE_ASM stmt. */
+
+class gasm_op : public gimple_stmt_op
+{
+public:
+ gasm_op (const gasm &asm_stmt)
+ : gimple_stmt_op (kind::asm_stmt, asm_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<gasm_op> (get_gasm ());
+ }
+
+ const gasm &get_gasm () const
+ {
+ return *as_a <const gasm *> (&get_stmt ());
+ }
+};
+
+/* An operation subclass representing the effect of a GIMPLE_ASSIGN stmt. */
+
+class gassign_op : public gimple_stmt_op
+{
+public:
+ gassign_op (const gassign &assign_stmt)
+ : gimple_stmt_op (kind::assignment, assign_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<gassign_op> (get_gassign ());
+ }
+
+ const gassign &get_gassign () const
+ {
+ return *as_a <const gassign *> (&get_stmt ());
+ }
+};
+
+/* An operation subclass for a GIMPLE_PREDICT stmt.
+ They have no effect on state, but can be useful for reconstructing
+ where "return" statements were in the code the user originally wrote,
+ to improve the reported locations in diagnostics. */
+
+class predict_op : public gimple_stmt_op
+{
+public:
+ predict_op (const gimple &predict_stmt)
+ : gimple_stmt_op (kind::predict_stmt, predict_stmt)
+ {
+ gcc_assert (predict_stmt.code == GIMPLE_PREDICT);
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<predict_op> (get_stmt ());
+ }
+};
+
+/* An operation subclass representing both:
+ (a) the effect of a GIMPLE_RETURN stmt: copying a value into the
+ RESULT_DECL of the current frame, and
+ (b) a hint when reporting diagnostics that this is the return
+ path from the function (rather than say, throwing an exception). */
+
+class greturn_op : public gimple_stmt_op
+{
+public:
+ greturn_op (const greturn &return_stmt)
+ : gimple_stmt_op (kind::return_stmt, return_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<greturn_op> (get_greturn ());
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+ bool
+ execute_for_feasibility (const exploded_edge &,
+ feasibility_state &,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+ bool
+ supports_bulk_merge_p () const final override
+ {
+ return false;
+ }
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const final override;
+
+ const greturn &get_greturn () const
+ {
+ return *as_a <const greturn *> (&get_stmt ());
+ }
+
+ tree get_retval () const
+ {
+ return gimple_return_retval (&get_greturn ());
+ }
+};
+
+/* A concrete operation subclass representing the effect of a GIMPLE_CALL stmt.
+
+ If the function is identified and has a known body, either simulate
+ it interprocedurally by pushing a stack frame and transitioning to the
+ callee, or simulate it intraprocedurally by replaying a summary of the
+ effects of the call.
+
+ If the function is identified but has an unknown body,
+ simulate it intraprocedurally, either using a known_function
+ subclass for precision, or following conservative rules that
+ assume various side-effects.
+
+ If the function is unidentified (for some kinds of dynamic calls),
+ simulate it intraprocedurally, following conservative rules that
+ assume various side-effects.
+
+ In the various intraprocedural simulation cases, the exploded edge will
+ correspond to the underlying superedge.
+
+ In the interprocedural simulation case, the exploded edge will
+ link two supernodes in different functions, and thus will require
+ custom edge info.
+
+ Various subclasses exist for handling awkward special cases,
+ such as longjmp. */
+
+class call_and_return_op : public gimple_stmt_op
+{
+public:
+ static std::unique_ptr<operation>
+ make (const gcall &call_stmt);
+
+ std::unique_ptr<operation>
+ clone () const override
+ {
+ return std::make_unique<call_and_return_op> (get_gcall ());
+ }
+
+ const gcall &get_gcall () const
+ {
+ return *as_a <const gcall *> (&get_stmt ());
+ }
+
+ void
+ execute (operation_context &op_ctxt) const override;
+
+ bool
+ supports_bulk_merge_p () const final override
+ {
+ return false;
+ }
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const override;
+
+ const call_and_return_op *
+ dyn_cast_call_and_return_op () const final override { return this; }
+
+ tree
+ map_expr_from_caller_to_callee (tree callee_fndecl,
+ tree caller_expr,
+ callsite_expr *out) const;
+ tree
+ map_expr_from_callee_to_caller (tree callee_fndecl,
+ tree callee_expr,
+ callsite_expr *out) const;
+
+ call_and_return_op (const gcall &call_stmt)
+ : gimple_stmt_op (kind::call_and_return, call_stmt)
+ {
+ }
+
+ const known_function *
+ maybe_get_known_function (const call_details &cd) const;
+
+private:
+ cgraph_edge *
+ get_any_cgraph_edge (operation_context &op_ctxt) const;
+
+ void
+ replay_call_summaries (operation_context &op_ctxt,
+ function &called_fn,
+ per_function_data &called_fn_data,
+ region_model_context *ctxt) const;
+
+ void
+ replay_call_summary (operation_context &op_ctxt,
+ function &called_fn,
+ call_summary &summary,
+ region_model_context *ctxt) const;
+
+ tree
+ get_arg_for_parm (tree callee_fndecl,
+ tree parm,
+ callsite_expr *out) const;
+ tree
+ get_parm_for_arg (tree callee_fndecl,
+ tree arg,
+ callsite_expr *out) const;
+};
+
+/* A call to one of the various __analyzer_dump* functions.
+ These have no effect on state. */
+
+class dump_op : public call_and_return_op
+{
+public:
+ enum dump_kind
+ {
+ state,
+ sarif,
+ dot,
+ state_2
+ };
+
+ dump_op (const gcall &call_stmt, enum dump_kind dump_kind_)
+ : call_and_return_op (call_stmt),
+ m_dump_kind (dump_kind_)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<dump_op> (get_gcall (), m_dump_kind);
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+private:
+ enum dump_kind m_dump_kind;
+};
+
+class setjmp_op : public call_and_return_op
+{
+public:
+ setjmp_op (const gcall &call_stmt)
+ : call_and_return_op (call_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<setjmp_op> (get_gcall ());
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const final override;
+};
+
+class longjmp_op : public call_and_return_op
+{
+public:
+ longjmp_op (const gcall &call_stmt)
+ : call_and_return_op (call_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<longjmp_op> (get_gcall ());
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+};
+
+class cxa_throw_op : public call_and_return_op
+{
+public:
+ cxa_throw_op (const gcall &call_stmt, bool is_rethrow)
+ : call_and_return_op (call_stmt),
+ m_is_rethrow (is_rethrow)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<cxa_throw_op> (get_gcall (), m_is_rethrow);
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+private:
+ bool m_is_rethrow;
+};
+
+class resx_op : public gimple_stmt_op
+{
+public:
+ resx_op (const gresx &resx_stmt)
+ : gimple_stmt_op (kind::resx, resx_stmt)
+ {
+ }
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<resx_op> (get_gresx ());
+ }
+
+ const gresx &get_gresx () const
+ {
+ return *as_a <const gresx *> (&get_stmt ());
+ }
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+ bool
+ supports_bulk_merge_p () const final override
+ {
+ return false;
+ }
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const final override;
+};
+
+/* An abstract subclass of operation representing the filtering effect on
+ state of a gimple control-flow statement at the end of a BB, for
+ a specific CFG out-edge from that BB. */
+
+class control_flow_op : public operation
+{
+public:
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const override;
+
+ bool
+ defines_ssa_name_p (const_tree) const final override
+ {
+ return false;
+ }
+
+ void
+ walk_load_store_addr_ops (void *,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn) const final override;
+
+ const gimple *
+ maybe_get_stmt () const final override
+ {
+ return &m_ctrlflow_stmt;
+ }
+
+ virtual label_text
+ maybe_describe_condition (bool can_colorize) const;
+
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+ bool
+ supports_bulk_merge_p () const final override
+ {
+ return false;
+ }
+
+ bool
+ execute_for_feasibility (const exploded_edge &,
+ feasibility_state &,
+ region_model_context *,
+ std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+ const control_flow_op *
+ dyn_cast_control_flow_op () const { return this; }
+
+ ::edge get_cfg_edge () const { return m_cfg_edge; }
+ int get_flags () const { return m_cfg_edge->flags; }
+ int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
+
+ const gimple &get_ctrlflow_stmt () const { return m_ctrlflow_stmt; }
+
+protected:
+ control_flow_op (enum kind kind_,
+ ::edge cfg_edge,
+ const gimple &ctrlflow_stmt)
+ : operation (kind_),
+ m_cfg_edge (cfg_edge),
+ m_ctrlflow_stmt (ctrlflow_stmt)
+ {}
+
+private:
+ virtual bool
+ apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out) const = 0;
+
+ ::edge m_cfg_edge;
+ const gimple &m_ctrlflow_stmt;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+ transitions on a specific CFG edge after a GIMPLE_COND stmt, either the
+ "if (cond) is true" or the "if (cond) is false" branch. */
+
+class gcond_edge_op : public control_flow_op
+{
+public:
+ gcond_edge_op (::edge cfg_edge,
+ const gcond &cond_stmt);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<gcond_edge_op> (get_cfg_edge (),
+ get_gcond ());
+ }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ label_text
+ maybe_describe_condition (bool can_colorize) const final override;
+
+ const gcond &get_gcond () const
+ {
+ return *as_a <const gcond *> (&get_ctrlflow_stmt ());
+ }
+
+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 expr);
+
+ bool
+ apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ bool m_true_value;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+ transitions on a specific CFG edge after a GIMPLE_GOTO stmt, thus
+ handling computed gotos. */
+
+class ggoto_edge_op : public control_flow_op
+{
+public:
+ ggoto_edge_op (::edge cfg_edge,
+ const ggoto &goto_stmt,
+ tree dst_label);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<ggoto_edge_op> (get_cfg_edge (),
+ get_ggoto (),
+ m_dst_label);
+ }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ label_text
+ maybe_describe_condition (bool can_colorize) const final override;
+
+ const ggoto &get_ggoto () const
+ {
+ return *as_a <const ggoto *> (&get_ctrlflow_stmt ());
+ }
+
+private:
+ bool
+ apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ tree m_dst_label;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+ transitions on a specific CFG edge after a GIMPLE_SWITCH stmt, thus
+ handling a cluster of cases/default value. */
+
+class switch_case_op : public control_flow_op
+{
+ public:
+ switch_case_op (function &fun,
+ ::edge cfg_edge,
+ const gswitch &switch_stmt,
+ bounded_ranges_manager &mgr);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<switch_case_op> (*this);
+ }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ bool implicitly_created_default_p () const;
+
+ const gswitch &get_gswitch () const
+ {
+ return *as_a <const gswitch *> (&get_ctrlflow_stmt ());
+ }
+
+ private:
+ bool
+ apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ std::vector<tree> m_case_labels;
+ const bounded_ranges *m_all_cases_ranges;
+};
+
+/* Abstract subclass for edges from eh_dispatch statements. */
+
+class eh_dispatch_edge_op : public control_flow_op
+{
+public:
+ static std::unique_ptr<eh_dispatch_edge_op>
+ make (supernode *src,
+ supernode *dest,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt);
+
+ const geh_dispatch &
+ get_geh_dispatch () const
+ {
+ return *as_a <const geh_dispatch *> (&get_ctrlflow_stmt ());
+ }
+
+ eh_region
+ get_eh_region () const { return m_eh_region; }
+
+protected:
+ eh_dispatch_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ enum kind kind_,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg);
+
+ supernode *get_src_snode () const { return m_src_snode; }
+
+private:
+ bool
+ apply_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ virtual bool
+ apply_eh_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const = 0;
+
+ supernode *m_src_snode;
+ supernode *m_dst_snode;
+ eh_region m_eh_region;
+};
+
+/* Concrete operation for edges from an eh_dispatch statement
+ for ERT_TRY regions. */
+
+class eh_dispatch_try_edge_op : public eh_dispatch_edge_op
+{
+public:
+ eh_dispatch_try_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg,
+ eh_catch ehc);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<eh_dispatch_try_edge_op> (*this);
+ }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const final override;
+
+private:
+ bool
+ apply_eh_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ eh_catch m_eh_catch;
+};
+
+/* Concrete operation for edges from an eh_dispatch statement
+ for ERT_ALLOWED_EXCEPTIONS regions. */
+
+class eh_dispatch_allowed_edge_op : public eh_dispatch_edge_op
+{
+public:
+ enum eh_kind
+ {
+ expected,
+ unexpected
+ };
+
+ eh_dispatch_allowed_edge_op (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge cfg_edge,
+ const geh_dispatch &geh_dispatch_stmt,
+ eh_region eh_reg);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<eh_dispatch_allowed_edge_op> (*this);
+ }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ enum eh_kind get_eh_kind () const { return m_kind; }
+
+private:
+ bool
+ apply_eh_constraints (const superedge *sedge,
+ region_model &model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ enum eh_kind m_kind;
+};
+
+/* Concrete operation subclass representing the state transition
+ for simultaneously handling all of the phi nodes at the entry to a BB,
+ after following a specific CFG in-edge.
+ Note that this covers multiple gimple stmts: all of the gphi stmts
+ at a basic block entry (albeit for just one in-edge).
+ This can be thought of as handling one column of the entries in the
+ phi nodes of a BB (for a specific in-edge).
+ We ignore MEM entries, and discard phi nodes purely affecting them. */
+
+class phis_for_edge_op : public operation
+{
+public:
+ /* A "dst=src;" pair within a phi node. */
+ struct pair
+ {
+ tree m_dst;
+ tree m_src;
+ };
+
+ static std::unique_ptr<operation>
+ maybe_make (::edge cfg_in_edge);
+
+ std::unique_ptr<operation>
+ clone () const final override
+ {
+ return std::make_unique<phis_for_edge_op> (*this);
+ }
+
+ phis_for_edge_op (std::vector<pair> &&pairs,
+ ::edge cfg_in_edge);
+
+ const phis_for_edge_op *
+ dyn_cast_phis_for_edge_op () const final override { return this; }
+
+ void
+ print_as_edge_label (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ bool
+ defines_ssa_name_p (const_tree ssa_name) const final override;
+
+ void
+ walk_load_store_addr_ops (void *,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn,
+ walk_stmt_load_store_addr_fn) const final override;
+ void
+ execute (operation_context &op_ctxt) const final override;
+
+ bool
+ execute_for_feasibility (const exploded_edge &,
+ feasibility_state &,
+ region_model_context *,
+ std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+ bool
+ supports_bulk_merge_p () const final override
+ {
+ return true;
+ }
+ void
+ update_state_for_bulk_merger (const program_state &src_state,
+ program_state &dst_state) const final override;
+
+ void
+ add_any_events_for_eedge (const exploded_edge &eedge,
+ checker_path &out_path) const final override;
+
+ const std::vector<pair> &get_pairs () const { return m_pairs; }
+
+private:
+ static std::vector<pair>
+ get_pairs_for_phi_along_in_edge (::edge cfg_in_edge);
+
+ void
+ update_state (const program_state &src_state,
+ program_state &dst_state,
+ region_model_context *ctxt) const;
+
+ std::vector<pair> m_pairs;
+ ::edge m_cfg_in_edge;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_OPS_H */
void
pending_diagnostic::add_call_event (const exploded_edge &eedge,
- checker_path *emission_path)
+ const gcall &,
+ checker_path &emission_path)
{
- const exploded_node *src_node = eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
- const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
- emission_path->add_event
+ emission_path.add_event
(std::make_unique<call_event> (eedge,
- event_loc_info (last_stmt
- ? last_stmt->location
- : UNKNOWN_LOCATION,
- src_point.get_fndecl (),
- src_stack_depth)));
+ event_loc_info (eedge.m_src)));
}
/* Base implementation of pending_diagnostic::add_region_creation_events.
checker_path *emission_path);
/* Vfunc for extending/overriding creation of the events for an
- exploded_edge that corresponds to a superedge, allowing for custom
- events to be created that are pertinent to a particular
- pending_diagnostic subclass.
+ exploded_edge, allowing for custom events to be created that are
+ pertinent to a particular pending_diagnostic subclass.
For example, the -Wanalyzer-stale-setjmp-buffer diagnostic adds a
custom event showing when the pertinent stack frame is popped
(and thus the point at which the jmp_buf becomes invalid). */
- virtual bool maybe_add_custom_events_for_superedge (const exploded_edge &,
- checker_path *)
+ virtual bool maybe_add_custom_events_for_eedge (const exploded_edge &,
+ checker_path *)
{
return false;
}
the varargs diagnostics can add a custom event subclass that annotates
the variadic arguments. */
virtual void add_call_event (const exploded_edge &,
- checker_path *);
+ const gcall &call_stmt,
+ checker_path &emission_path);
/* Vfunc for adding any events for the creation of regions identified
by the mark_interesting_stuff vfunc.
/* Vfunc to give diagnostic subclasses the opportunity to reject diagnostics
by imposing their own additional feasibility checks on the path to a
given feasible_node. */
- virtual bool check_valid_fpath_p (const feasible_node &,
- const gimple *) const
+ virtual bool check_valid_fpath_p (const feasible_node &) const
{
/* Default implementation: accept this path. */
return true;
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");
- if (f.m_newlines)
- pp_newline (pp);
- break;
-
- case PK_BEFORE_SUPERNODE:
- {
- if (m_from_edge)
- {
- if (basic_block bb = m_from_edge->m_src->m_bb)
- pp_printf (pp, "before SN: %i (from SN: %i (bb: %i))",
- m_supernode->m_index, m_from_edge->m_src->m_index,
- bb->index);
- else
- 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);
- if (f.m_newlines)
- pp_newline (pp);
- 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 nullptr;
-}
-
-/* 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 nullptr;
-}
-
-/* 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;
-}
-
-/* Return true if this function_point is a before_stmt for
- the final stmt in its supernode. */
-
-bool
-function_point::final_stmt_p () const
-{
- if (m_kind != PK_BEFORE_STMT)
- return false;
- return m_stmt_idx == get_supernode ()->m_stmts.length () - 1;
-}
-
-/* Create a function_point representing the entrypoint of function FUN. */
-
-function_point
-function_point::from_function_entry (const supergraph &sg, const function &fun)
-{
- return before_supernode (sg.get_node_for_function_entry (fun), nullptr);
-}
-
-/* Create a function_point representing entering supernode SUPERNODE,
- having reached it via FROM_EDGE (which could be nullptr). */
-
-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 = nullptr;
- return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
-}
-
/* A subclass of diagnostics::context for use by
program_point::print_source_line. */
}
};
-/* Print the source line (if any) for this function_point to PP. */
+/* class program_point. */
+
+/* Print the source line (if any) for this program_point to PP. */
void
-function_point::print_source_line (pretty_printer *pp) const
+program_point::print_source_line (pretty_printer *pp) const
{
- const gimple *stmt = get_stmt ();
- if (!stmt)
+ if (!useful_location_p (get_location ()))
return;
- // TODO: monospace font
debug_diagnostic_context tmp_dc;
- gcc_rich_location richloc (stmt->location);
+ gcc_rich_location richloc (get_location ());
diagnostics::source_print_policy source_policy (tmp_dc);
gcc_assert (pp);
source_policy.print (*pp, richloc, diagnostics::kind::error, nullptr);
pp_string (pp, pp_formatted_text (tmp_dc.get_reference_printer ()));
}
-/* class program_point. */
-
/* Print this program_point to PP. */
void
m_call_string->print (pp);
f.spacer (pp);
- m_function_point.print (pp, f);
+ if (m_snode)
+ {
+ pp_printf (pp, "sn: %i", m_snode->m_id);
+ if (f.m_newlines)
+ {
+ pp_newline (pp);
+ print_source_line (pp);
+ }
+ }
+ else
+ pp_string (pp, "origin");
}
/* Dump this point to stderr. */
}
/* 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',
+ {"snode_idx" : int (optional), the index of the supernode,
"call_string": object for the call_string}. */
std::unique_ptr<json::object>
{
auto point_obj = std::make_unique<json::object> ();
- point_obj->set_string ("kind", point_kind_to_string (get_kind ()));
-
if (get_supernode ())
- point_obj->set_integer ("snode_idx", get_supernode ()->m_index);
-
- switch (get_kind ())
- {
- default: break;
- case PK_BEFORE_SUPERNODE:
- if (const superedge *sedge = get_from_edge ())
- point_obj->set_integer ("from_edge_snode_idx", sedge->m_src->m_index);
- break;
- case PK_BEFORE_STMT:
- point_obj->set_integer ("stmt_idx", get_stmt_idx ());
- break;
- }
-
+ point_obj->set_integer ("snode_idx", get_supernode ()->m_id);
point_obj->set ("call_string", m_call_string->to_json ());
return point_obj;
}
-/* Update the callstack to represent a call from caller to callee.
-
- Generally used to push a custom call to a perticular program point
- where we don't have a superedge representing the call. */
-void
-program_point::push_to_call_stack (const supernode *caller,
- const supernode *callee)
-{
- m_call_string = m_call_string->push_call (callee, caller);
-}
-
/* Pop the topmost call from the current callstack. */
void
program_point::pop_from_call_stack ()
program_point::hash () const
{
inchash::hash hstate;
- hstate.merge_hash (m_function_point.hash ());
+ hstate.add_ptr (m_snode);
hstate.add_ptr (m_call_string);
return hstate.end ();
}
{
gcc_assert (depth <= m_call_string->length ());
if (depth == m_call_string->length ())
- return m_function_point.get_function ();
+ return m_snode->get_function ();
else
return get_call_string ()[depth].get_caller_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);
-
- if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
- {
- const supernode *src_snode = cfg_sedge->m_src;
- if (gimple *last_stmt = src_snode->get_last_stmt ())
- if (last_stmt->code == GIMPLE_GOTO)
- {
- /* For the program_point aspect here, consider all
- out-edges from goto stmts to be valid; we'll
- consider state separately. */
- return true;
- }
-
- /* Reject other kinds of abnormal edges;
- we special-case setjmp/longjmp. */
- 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 = 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 call_string::element_t &top_of_stack
- = m_call_string->get_top_of_stack ();
- m_call_string = m_call_string->get_parent ();
- call_string::element_t current_call_string_element (succ->m_dest,
- succ->m_src);
- if (top_of_stack != current_call_string_element)
- {
- 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 function points for which there is a uniquely-defined
- successor, return it. */
-
-function_point
-function_point::get_next () const
-{
- switch (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);
- else
- return after_supernode (get_supernode ());
- case PK_BEFORE_STMT:
- {
- unsigned next_idx = get_stmt_idx () + 1;
- if (next_idx < get_supernode ()->m_stmts.length ())
- return before_stmt (get_supernode (), next_idx);
- else
- return after_supernode (get_supernode ());
- }
- }
-}
-
/* class program_point. */
program_point
program_point::origin (const region_model_manager &mgr)
{
- return program_point (function_point (nullptr, nullptr,
- 0, PK_ORIGIN),
+ return program_point (nullptr,
mgr.get_empty_call_string ());
}
const supergraph &sg,
const function &fun)
{
- return program_point (function_point::from_function_entry (sg, fun),
+ return program_point (sg.get_node_for_function_entry (fun),
mgr.get_empty_call_string ());
}
-/* 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 () + 1;
- 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 ());
- }
- }
-}
-
/* Return true iff POINT_A and POINT_B share the same function and
call_string, both directly, and when attempting to undo inlining
information. */
namespace selftest {
-/* Verify that function_point::operator== works as expected. */
-
-static void
-test_function_point_equality ()
-{
- const supernode *snode = nullptr;
-
- function_point a = function_point (snode, nullptr, 0,
- PK_BEFORE_SUPERNODE);
- function_point b = function_point::before_supernode (snode, nullptr);
- ASSERT_EQ (a, b);
-}
-
-/* Verify that function_point::cmp_within_supernode works as expected. */
-
-static void
-test_function_point_ordering ()
-{
- const supernode *snode = nullptr;
-
- /* 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, nullptr));
- 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
const call_string &cs = mgr.get_empty_call_string ();
- program_point a = program_point::before_supernode (snode, nullptr,
- cs);
-
- program_point b = program_point::before_supernode (snode, nullptr,
- cs);
-
+ program_point a = program_point (snode, cs);
+ program_point b = program_point (snode, cs);
ASSERT_EQ (a, b);
- // TODO: verify with non-empty callstrings, with different edges
+ // TODO: verify with non-empty callstrings, with different snodes
}
/* 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 ();
}
#include "pretty-print.h"
#include "analyzer/call-string.h"
+#include "analyzer/supergraph.h"
namespace ana {
-class exploded_graph;
-
-/* An enum for distinguishing the various kinds of program_point. */
-
-enum point_kind {
- /* A "fake" node which has edges to all entrypoints. */
- PK_ORIGIN,
-
- PK_BEFORE_SUPERNODE,
- PK_BEFORE_STMT,
- PK_AFTER_SUPERNODE,
-
- /* Special values used for hash_map: */
- PK_EMPTY,
- PK_DELETED,
-
- NUM_POINT_KINDS
-};
-
-extern const char *point_kind_to_string (enum point_kind pk);
-
class format
{
public:
bool m_newlines;
};
-/* A class for representing a location within the program, without
- interprocedural information.
-
- This represents a fine-grained location within the supergraph (or
- within one of its nodes). */
-
-class function_point
-{
-public:
- function_point (const supernode *supernode,
- const superedge *from_edge,
- unsigned stmt_idx,
- enum point_kind kind);
-
- void print (pretty_printer *pp, const format &f) const;
- void print_source_line (pretty_printer *pp) const;
- void dump () const;
-
- hashval_t hash () const;
- bool operator== (const function_point &other) const
- {
- return (m_supernode == other.m_supernode
- && m_from_edge == other.m_from_edge
- && m_stmt_idx == other.m_stmt_idx
- && m_kind == other.m_kind);
- }
-
- /* Accessors. */
-
- const supernode *get_supernode () const { return m_supernode; }
- function *get_function () const;
- const gimple *get_stmt () const;
- location_t get_location () const;
- enum point_kind get_kind () const { return m_kind; }
- const superedge *get_from_edge () const
- {
- return m_from_edge;
- }
- unsigned get_stmt_idx () const
- {
- gcc_assert (m_kind == PK_BEFORE_STMT);
- return m_stmt_idx;
- }
-
- bool final_stmt_p () const;
-
- /* Factory functions for making various kinds of program_point. */
-
- static function_point from_function_entry (const supergraph &sg,
- const function &fun);
-
- static function_point before_supernode (const supernode *supernode,
- const superedge *from_edge);
-
- static function_point before_stmt (const supernode *supernode,
- unsigned stmt_idx)
- {
- return function_point (supernode, nullptr, stmt_idx, PK_BEFORE_STMT);
- }
-
- static function_point after_supernode (const supernode *supernode)
- {
- return function_point (supernode, nullptr, 0, PK_AFTER_SUPERNODE);
- }
-
- /* Support for hash_map. */
-
- static function_point empty ()
- {
- return function_point (nullptr, nullptr, 0, PK_EMPTY);
- }
- static function_point deleted ()
- {
- return function_point (nullptr, nullptr, 0, PK_DELETED);
- }
-
- static int cmp_within_supernode_1 (const function_point &point_a,
- const function_point &point_b);
- static int cmp_within_supernode (const function_point &point_a,
- const function_point &point_b);
- static int cmp (const function_point &point_a,
- const function_point &point_b);
- static int cmp_ptr (const void *p1, const void *p2);
-
- /* For before_stmt, go to next stmt. */
- void next_stmt ();
-
- function_point get_next () const;
-
- private:
- const supernode *m_supernode;
-
- /* For PK_BEFORE_SUPERNODE, and only for CFG edges. */
- const superedge *m_from_edge;
-
- /* Only for PK_BEFORE_STMT. */
- unsigned m_stmt_idx;
-
- enum point_kind m_kind;
-};
-
/* A class for representing a location within the program, including
interprocedural information.
class program_point
{
public:
- program_point (const function_point &fn_point,
+ program_point (const supernode *snode,
const call_string &call_string)
- : m_function_point (fn_point),
+ : m_snode (snode),
m_call_string (&call_string)
{
}
void print (pretty_printer *pp, const format &f) const;
void dump () const;
+ void print_source_line (pretty_printer *pp) const;
std::unique_ptr<json::object> to_json () const;
hashval_t hash () const;
bool operator== (const program_point &other) const
{
- return (m_function_point == other.m_function_point
+ return (m_snode == other.m_snode
&& m_call_string == other.m_call_string);
}
bool operator!= (const program_point &other) const
/* Accessors. */
- const function_point &get_function_point () const { return m_function_point; }
- const call_string &get_call_string () const { return *m_call_string; }
-
const supernode *get_supernode () const
{
- return m_function_point.get_supernode ();
+ return m_snode;
}
+ const call_string &get_call_string () const { return *m_call_string; }
+
function *get_function () const
{
- return m_function_point.get_function ();
+ return m_snode ? m_snode->get_function () : nullptr;
}
function *get_function_at_depth (unsigned depth) const;
tree get_fndecl () const
{
- gcc_assert (get_kind () != PK_ORIGIN);
- return get_function ()->decl;
- }
- const gimple *get_stmt () const
- {
- return m_function_point.get_stmt ();
+ function *fn = get_function ();
+ return fn ? fn->decl : nullptr;
}
location_t get_location () const
{
- return m_function_point.get_location ();
- }
- enum point_kind get_kind () const
- {
- return m_function_point.get_kind ();
- }
- const superedge *get_from_edge () const
- {
- return m_function_point.get_from_edge ();
- }
- unsigned get_stmt_idx () const
- {
- return m_function_point.get_stmt_idx ();
+ return m_snode ? m_snode->get_location () : UNKNOWN_LOCATION;
}
/* Get the number of frames we expect at this program point.
node, which doesn't have any frames. */
int get_stack_depth () const
{
- if (get_kind () == PK_ORIGIN)
+ if (m_snode == nullptr)
+ // Origin
return 0;
return get_call_string ().length () + 1;
}
+ bool state_merge_at_p () const
+ {
+ if (m_snode)
+ return m_snode->m_state_merger_node;
+ return false;
+ }
+
/* Factory functions for making various kinds of program_point. */
static program_point origin (const region_model_manager &mgr);
static program_point from_function_entry (const region_model_manager &mgr,
const supergraph &sg,
const function &fun);
- static program_point before_supernode (const supernode *supernode,
- const superedge *from_edge,
- const call_string &call_string)
- {
- return program_point (function_point::before_supernode (supernode,
- from_edge),
- call_string);
- }
-
- static program_point before_stmt (const supernode *supernode,
- unsigned stmt_idx,
- const call_string &call_string)
- {
- return program_point (function_point::before_stmt (supernode, stmt_idx),
- call_string);
- }
-
- static program_point after_supernode (const supernode *supernode,
- const call_string &call_string)
- {
- return program_point (function_point::after_supernode (supernode),
- call_string);
- }
-
- /* Support for hash_map. */
-
- static program_point empty ()
- {
- return program_point (function_point::empty ());
- }
- static program_point deleted ()
- {
- return program_point (function_point::deleted ());
- }
-
- bool on_edge (exploded_graph &eg, const superedge *succ);
- void push_to_call_stack (const supernode *caller, const supernode *callee);
void pop_from_call_stack ();
void validate () const;
- /* For before_stmt, go to next stmt. */
- void next_stmt () { m_function_point.next_stmt (); }
-
- program_point get_next () const;
-
static bool effectively_intraprocedural_p (const program_point &point_a,
const program_point &point_b);
private:
- program_point (const function_point &fn_point)
- : m_function_point (fn_point),
- m_call_string (nullptr)
- {
- }
-
- function_point m_function_point;
+ const supernode *m_snode;
const call_string *m_call_string;
};
return m_region_model->get_current_function ();
}
-/* Determine if following edge SUCC from ENODE is valid within the graph EG
- and update this state accordingly in-place.
-
- Return true if the edge can be followed, or false otherwise.
-
- Check for relevant conditionals and switch-values for conditionals
- and switch statements, adding the relevant conditions to this state.
- Push/pop frames for interprocedural edges and update params/returned
- values.
-
- This is the "state" half of exploded_node::on_edge. */
-
-bool
-program_state::on_edge (exploded_graph &eg,
- exploded_node *enode,
- const superedge *succ,
- uncertainty_t *uncertainty)
-{
- class my_path_context : public path_context
- {
- public:
- my_path_context (bool &terminated) : m_terminated (terminated) {}
- void bifurcate (std::unique_ptr<custom_edge_info>) final override
- {
- gcc_unreachable ();
- }
-
- void terminate_path () final override
- {
- m_terminated = true;
- }
-
- bool terminate_path_p () const final override
- {
- return m_terminated;
- }
- bool &m_terminated;
- };
-
- /* Update state. */
- const program_point &point = enode->get_point ();
- const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
- /* For conditionals and switch statements, add the
- relevant conditions (for the specific edge) to new_state;
- skip edges for which the resulting constraints
- are impossible.
- This also updates frame information for call/return superedges.
- Adding the relevant conditions for the edge could also trigger
- sm-state transitions (e.g. transitions due to ptrs becoming known
- to be NULL or non-NULL) */
- bool terminated = false;
- my_path_context path_ctxt (terminated);
- impl_region_model_context ctxt (eg, enode,
- &enode->get_state (),
- this,
- uncertainty, &path_ctxt,
- last_stmt);
- std::unique_ptr<rejected_constraint> rc;
- logger * const logger = eg.get_logger ();
- if (!m_region_model->maybe_update_for_edge (*succ,
- last_stmt,
- &ctxt,
- logger ? &rc : nullptr))
- {
- if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("edge to SN: %i is impossible"
- " due to region_model constraint: ",
- succ->m_dest->m_index);
- rc->dump_to_pp (logger->get_printer ());
- logger->end_log_line ();
- }
- return false;
- }
- if (terminated)
- return false;
-
- program_state::detect_leaks (enode->get_state (), *this,
- nullptr, eg.get_ext_state (),
- &ctxt);
-
- return true;
-}
-
-/* Update this program_state to reflect a call to function
- represented by CALL_STMT.
- currently used only when the call doesn't have a superedge representing
- the call ( like call via a function pointer ) */
-void
-program_state::push_call (exploded_graph &eg,
- exploded_node *enode,
- const gcall &call_stmt,
- uncertainty_t *uncertainty)
-{
- /* Update state. */
- const program_point &point = enode->get_point ();
- const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
- impl_region_model_context ctxt (eg, enode,
- &enode->get_state (),
- this,
- uncertainty,
- nullptr,
- last_stmt);
- m_region_model->update_for_gcall (call_stmt, &ctxt);
-}
-
-/* Update this program_state to reflect a return from function
- call to which is represented by CALL_STMT.
- currently used only when the call doesn't have a superedge representing
- the return */
-void
-program_state::returning_call (exploded_graph &eg,
- exploded_node *enode,
- const gcall &call_stmt,
- uncertainty_t *uncertainty)
-{
- /* Update state. */
- const program_point &point = enode->get_point ();
- const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
- impl_region_model_context ctxt (eg, enode,
- &enode->get_state (),
- this,
- uncertainty,
- nullptr,
- last_stmt);
- m_region_model->update_for_return_gcall (call_stmt, &ctxt);
-}
-
/* Generate a simpler version of THIS, discarding state that's no longer
relevant at POINT.
The idea is that we're more likely to be able to consolidate
const tree ssa_name = node;
const state_purge_per_ssa_name &per_ssa
= pm->get_data_for_ssa_name (node);
- if (!per_ssa.needed_at_point_p (point.get_function_point ()))
+ if (!per_ssa.needed_at_supernode_p (point.get_supernode ()))
{
/* Don't purge bindings of SSA names to svalues
that have unpurgable sm-state, so that leaks are
|| TREE_CODE (node) == RESULT_DECL);
if (const state_purge_per_decl *per_decl
= pm->get_any_data_for_decl (decl))
- if (!per_decl->needed_at_point_p (point.get_function_point ()))
+ if (!per_decl->needed_at_supernode_p (point.get_supernode ()))
{
/* Don't purge bindings of decls if there are svalues
that have unpurgable sm-state within the decl's cluster,
impl_region_model_context ctxt (eg, enode_for_diag,
this,
&new_state,
- uncertainty, nullptr,
- point.get_stmt ());
+ uncertainty,
+ nullptr);
detect_leaks (*this, new_state, nullptr, eg.get_ext_state (), &ctxt);
}
}
std::vector<std::unique_ptr<state_machine>> checkers;
const state_machine &borrowed_sm = *sm.get ();
checkers.push_back (std::move (sm));
- engine eng;
+ region_model_manager mgr;
+ engine eng (mgr);
extrinsic_state ext_state (std::move (checkers), &eng);
/* Test setting states on svalue_id instances directly. */
{
const state_machine::state test_state_42 ("test state 42", 42);
const state_machine::state_t TEST_STATE_42 = &test_state_42;
- region_model_manager mgr;
region_model model (&mgr);
const svalue *x_sval = model.get_rvalue (x, nullptr);
const svalue *y_sval = model.get_rvalue (y, nullptr);
/* Test setting states via equivalence classes. */
{
- region_model_manager mgr;
region_model model (&mgr);
const svalue *x_sval = model.get_rvalue (x, nullptr);
const svalue *y_sval = model.get_rvalue (y, nullptr);
/* Test equality and hashing. */
{
- region_model_manager mgr;
region_model model (&mgr);
const svalue *y_sval = model.get_rvalue (y, nullptr);
const svalue *z_sval = model.get_rvalue (z, nullptr);
ASSERT_EQ (map0.hash (), map1.hash ());
ASSERT_EQ (map0, map1);
- region_model_manager mgr;
region_model model (&mgr);
const svalue *x_sval = model.get_rvalue (x, nullptr);
const svalue *y_sval = model.get_rvalue (y, nullptr);
const state_machine::state_t UNCHECKED_STATE
= sm->get_state_by_name ("unchecked");
- engine eng;
+ region_model_manager mgr;
+ engine eng (mgr);
extrinsic_state ext_state (std::move (sm), &eng);
- region_model_manager *mgr = eng.get_model_manager ();
program_state s (ext_state);
region_model *model = s.m_region_model;
const svalue *size_in_bytes
- = mgr->get_or_create_unknown_svalue (size_type_node);
+ = mgr.get_or_create_unknown_svalue (size_type_node);
const region *new_reg
= model->get_or_create_region_for_heap_alloc (size_in_bytes, nullptr);
- const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
+ const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg);
model->set_value (model->get_lvalue (p, nullptr),
ptr_sval, nullptr);
sm_state_map *smap = s.m_checker_states[0];
tree string_cst_ptr = build_string_literal (4, "foo");
std::vector<std::unique_ptr<state_machine>> checkers;
- engine eng;
+ region_model_manager mgr;
+ engine eng (mgr);
extrinsic_state ext_state (std::move (checkers), &eng);
program_state s (ext_state);
malloc sm-state, pointing to a region on the heap. */
tree p = build_global_decl ("p", ptr_type_node);
- engine eng;
- region_model_manager *mgr = eng.get_model_manager ();
- program_point point (program_point::origin (*mgr));
+ region_model_manager mgr;
+ engine eng (mgr);
+ program_point point (program_point::origin (mgr));
extrinsic_state ext_state (make_malloc_state_machine (nullptr),
&eng);
region_model *model0 = s0.m_region_model;
const svalue *size_in_bytes
- = mgr->get_or_create_unknown_svalue (size_type_node);
+ = mgr.get_or_create_unknown_svalue (size_type_node);
const region *new_reg
= model0->get_or_create_region_for_heap_alloc (size_in_bytes, nullptr);
- const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
+ const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg);
model0->set_value (model0->get_lvalue (p, &ctxt),
ptr_sval, &ctxt);
sm_state_map *smap = s0.m_checker_states[0];
static void
test_program_state_merging_2 ()
{
- engine eng;
- region_model_manager *mgr = eng.get_model_manager ();
- program_point point (program_point::origin (*mgr));
+ region_model_manager mgr;
+ engine eng (mgr);
+ program_point point (program_point::origin (mgr));
extrinsic_state ext_state (make_signal_state_machine (nullptr), &eng);
const state_machine::state test_state_0 ("test state 0", 0);
void push_frame (const extrinsic_state &ext_state, const function &fun);
const function * get_current_function () const;
- void push_call (exploded_graph &eg,
- exploded_node *enode,
- const gcall &call_stmt,
- uncertainty_t *uncertainty);
-
- void returning_call (exploded_graph &eg,
- exploded_node *enode,
- const gcall &call_stmt,
- uncertainty_t *uncertainty);
-
-
- bool on_edge (exploded_graph &eg,
- exploded_node *enode,
- const superedge *succ,
- uncertainty_t *uncertainty);
-
program_state prune_for_point (exploded_graph &eg,
const program_point &point,
exploded_node *enode_for_diag,
}
/* Constants. */
- if (tree cst = arg->maybe_get_constant ())
- if (tree result = fold_unary (op, type, cst))
- {
- if (CONSTANT_CLASS_P (result))
- return get_or_create_constant_svalue (result);
-
- /* fold_unary can return casts of constants; try to handle them. */
- if (op != NOP_EXPR
- && type
- && TREE_CODE (result) == NOP_EXPR
- && CONSTANT_CLASS_P (TREE_OPERAND (result, 0)))
- {
- const svalue *inner_cst
- = get_or_create_constant_svalue (TREE_OPERAND (result, 0));
- return get_or_create_cast (type,
- get_or_create_cast (TREE_TYPE (result),
- inner_cst));
- }
- }
+ if (type)
+ if (tree cst = arg->maybe_get_constant ())
+ if (tree result = fold_unary (op, type, cst))
+ {
+ if (CONSTANT_CLASS_P (result))
+ return get_or_create_constant_svalue (result);
+
+ /* fold_unary can return casts of constants; try to handle them. */
+ if (op != NOP_EXPR
+ && TREE_CODE (result) == NOP_EXPR
+ && CONSTANT_CLASS_P (TREE_OPERAND (result, 0)))
+ {
+ const svalue *inner_cst
+ = get_or_create_constant_svalue (TREE_OPERAND (result, 0));
+ return get_or_create_cast (type,
+ get_or_create_cast (TREE_TYPE (result),
+ inner_cst));
+ }
+ }
return nullptr;
}
}
/* Return the svalue * of type TYPE for the merger of value BASE_SVAL
- and ITER_SVAL at POINT, creating it if necessary. */
+ and ITER_SVAL at SNODE, creating it if necessary. */
const svalue *
region_model_manager::
get_or_create_widening_svalue (tree type,
- const function_point &point,
+ const supernode *snode,
const svalue *base_sval,
const svalue *iter_sval)
{
gcc_assert (base_sval->get_kind () != SK_WIDENING);
gcc_assert (iter_sval->get_kind () != SK_WIDENING);
- widening_svalue::key_t key (type, point, base_sval, iter_sval);
+ widening_svalue::key_t key (type, snode, base_sval, iter_sval);
if (widening_svalue **slot = m_widening_values_map.get (key))
return *slot;
widening_svalue *widening_sval
- = new widening_svalue (alloc_symbol_id (), type, point, base_sval,
+ = new widening_svalue (alloc_symbol_id (), type, snode, base_sval,
iter_sval);
RETURN_UNKNOWN_IF_TOO_COMPLEX (widening_sval);
m_widening_values_map.put (key, widening_sval);
const svalue *inner_svalue);
const svalue *get_or_create_unmergeable (const svalue *arg);
const svalue *get_or_create_widening_svalue (tree type,
- const function_point &point,
+ const supernode *snode,
const svalue *base_svalue,
const svalue *iter_svalue);
const svalue *get_or_create_compound_svalue (tree type,
heap-allocated regions, the numbering could be different).
Hence we access m_check_expr, if available. */
- bool check_valid_fpath_p (const feasible_node &fnode,
- const gimple *emission_stmt)
+ bool check_valid_fpath_p (const feasible_node &fnode)
const final override
{
if (!m_check_expr)
return true;
-
- /* We've reached the enode, but not necessarily the right function_point.
- Try to get the state at the correct stmt. */
- region_model emission_model (fnode.get_model ().get_manager());
- if (!fnode.get_state_at_stmt (emission_stmt, &emission_model))
- /* Couldn't get state; accept this diagnostic. */
- return true;
-
- const svalue *fsval = emission_model.get_rvalue (m_check_expr, nullptr);
+ const svalue *fsval = fnode.get_model ().get_rvalue (m_check_expr, nullptr);
/* Check to see if the expr is also poisoned in FNODE (and in the
same way). */
const poisoned_svalue * fspval = fsval->dyn_cast_poisoned_svalue ();
void
add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override
+ const exploded_edge &eedge,
+ pending_diagnostic &) const final override
{
const exploded_node *dst_node = eedge.m_dest;
const program_point &dst_point = dst_node->get_point ();
ctxt->bifurcate (std::move (throws_exception));
}
+/* A subclass of pending_diagnostic for complaining about jumps through NULL
+ function pointers. */
+
+class jump_through_null : public pending_diagnostic_subclass<jump_through_null>
+{
+public:
+ jump_through_null (const gcall &call)
+ : m_call (call)
+ {}
+
+ const char *get_kind () const final override
+ {
+ return "jump_through_null";
+ }
+
+ bool operator== (const jump_through_null &other) const
+ {
+ return &m_call == &other.m_call;
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_jump_through_null;
+ }
+
+ bool emit (diagnostic_emission_context &ctxt) final override
+ {
+ return ctxt.warn ("jump through null pointer");
+ }
+
+ bool describe_final_event (pretty_printer &pp,
+ const evdesc::final_event &) final override
+ {
+ pp_string (&pp, "jump through null pointer here");
+ return true;
+ }
+
+private:
+ const gcall &m_call;
+};
/* Update this model for the CALL stmt, using CTXT to report any
diagnostics - the first half.
if (!callee_fndecl)
{
+ /* Check for jump through nullptr. */
+ if (ctxt)
+ if (tree fn_ptr = gimple_call_fn (&call))
+ {
+ const svalue *fn_ptr_sval = get_rvalue (fn_ptr, ctxt);
+ if (fn_ptr_sval->all_zeroes_p ())
+ {
+ ctxt->warn
+ (std::make_unique<jump_through_null> (call));
+ ctxt->terminate_path ();
+ return true;
+ }
+ }
+
check_for_throw_inside_call (call, NULL_TREE, ctxt);
cd.set_any_lhs_with_defaults ();
return true; /* Unknown side effects. */
0), as opposed to any second return due to longjmp/sigsetjmp. */
void
-region_model::on_setjmp (const gcall &call, const exploded_node *enode,
+region_model::on_setjmp (const gcall &call,
+ const exploded_node &enode,
+ const superedge &sedge,
region_model_context *ctxt)
{
const svalue *buf_ptr = get_rvalue (gimple_call_arg (&call, 0), ctxt);
region. */
if (buf_reg)
{
- setjmp_record r (enode, call);
+ setjmp_record r (&enode, &sedge, call);
const svalue *sval
= m_mgr->get_or_create_setjmp_svalue (r, buf_reg->get_type ());
set_value (buf_reg, sval, ctxt);
}
}
-/* Update this region_model for a phi stmt of the form
- LHS = PHI <...RHS...>.
- where RHS is for the appropriate edge.
- Get state from OLD_STATE so that all of the phi stmts for a basic block
- are effectively handled simultaneously. */
-
-void
-region_model::handle_phi (const gphi *phi,
- tree lhs, tree rhs,
- const region_model &old_state,
- hash_set<const svalue *> &svals_changing_meaning,
- region_model_context *ctxt)
-{
- /* For now, don't bother tracking the .MEM SSA names. */
- if (tree var = SSA_NAME_VAR (lhs))
- if (TREE_CODE (var) == VAR_DECL)
- if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
- return;
-
- const svalue *src_sval = old_state.get_rvalue (rhs, ctxt);
- const region *dst_reg = old_state.get_lvalue (lhs, ctxt);
-
- const svalue *sval = old_state.get_rvalue (lhs, nullptr);
- if (sval->get_kind () == SK_WIDENING)
- svals_changing_meaning.add (sval);
-
- set_value (dst_reg, src_sval, ctxt);
-
- if (ctxt)
- ctxt->on_phi (phi, rhs);
-}
-
/* Implementation of region_model::get_lvalue; the latter adds type-checking.
Get the id of the region for PV within this region_model,
return result;
}
-/* Update this model for any phis in SNODE, assuming we came from
- LAST_CFG_SUPEREDGE. */
-
-void
-region_model::update_for_phis (const supernode *snode,
- const cfg_superedge *last_cfg_superedge,
- region_model_context *ctxt)
-{
- gcc_assert (last_cfg_superedge);
-
- /* Copy this state and pass it to handle_phi so that all of the phi stmts
- are effectively handled simultaneously. */
- const region_model old_state (*this);
-
- hash_set<const svalue *> svals_changing_meaning;
-
- for (gphi_iterator gpi = const_cast<supernode *>(snode)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
- {
- gphi *phi = gpi.phi ();
-
- tree src = last_cfg_superedge->get_phi_arg (phi);
- tree lhs = gimple_phi_result (phi);
-
- /* Update next_state based on phi and old_state. */
- handle_phi (phi, lhs, src, old_state, svals_changing_meaning, ctxt);
- }
-
- for (auto iter : svals_changing_meaning)
- m_constraints->purge_state_involving (iter);
-}
-
-/* Attempt to update this model for taking EDGE (where the last statement
- was LAST_STMT), returning true if the edge can be taken, false
- otherwise.
- When returning false, if OUT is non-NULL, write a new rejected_constraint
- to it.
-
- For CFG superedges where LAST_STMT is a conditional or a switch
- statement, attempt to add the relevant conditions for EDGE to this
- model, returning true if they are feasible, or false if they are
- impossible.
-
- For call superedges, push frame information and store arguments
- into parameters.
-
- For return superedges, pop frame information and store return
- values into any lhs.
-
- Rejection of call/return superedges happens elsewhere, in
- program_point::on_edge (i.e. based on program point, rather
- than program state). */
-
-bool
-region_model::maybe_update_for_edge (const superedge &edge,
- const gimple *last_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out)
-{
- /* Handle frame updates for interprocedural edges. */
- switch (edge.m_kind)
- {
- default:
- break;
-
- case SUPEREDGE_CALL:
- {
- const call_superedge *call_edge = as_a <const call_superedge *> (&edge);
- update_for_call_superedge (*call_edge, ctxt);
- }
- break;
-
- case SUPEREDGE_RETURN:
- {
- const return_superedge *return_edge
- = as_a <const return_superedge *> (&edge);
- update_for_return_superedge (*return_edge, ctxt);
- }
- break;
-
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- /* This is a no-op for call summaries; we should already
- have handled the effect of the call summary at the call stmt. */
- break;
- }
-
- if (last_stmt == nullptr)
- return true;
-
- /* Apply any constraints for conditionals/switch/computed-goto statements. */
-
- if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
- {
- const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
- return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt, out);
- }
-
- if (const gswitch *switch_stmt = dyn_cast <const gswitch *> (last_stmt))
- {
- const switch_cfg_superedge *switch_sedge
- = as_a <const switch_cfg_superedge *> (&edge);
- return apply_constraints_for_gswitch (*switch_sedge, switch_stmt,
- ctxt, out);
- }
-
- if (const geh_dispatch *eh_dispatch_stmt
- = dyn_cast <const geh_dispatch *> (last_stmt))
- {
- const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge
- = as_a <const eh_dispatch_cfg_superedge *> (&edge);
- return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge,
- eh_dispatch_stmt,
- ctxt, out);
- }
-
- if (const ggoto *goto_stmt = dyn_cast <const ggoto *> (last_stmt))
- {
- const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
- return apply_constraints_for_ggoto (*cfg_sedge, goto_stmt, ctxt);
- }
-
- return true;
-}
-
/* Push a new frame_region on to the stack region.
Populate the frame_region with child regions for the function call's
parameters, using values from the arguments at the callsite in the
pop_frame (lhs, nullptr, ctxt, &call_stmt);
}
-/* Extract calling information from the superedge and update the model for the
- call */
-
-void
-region_model::update_for_call_superedge (const call_superedge &call_edge,
- region_model_context *ctxt)
-{
- const gcall &call_stmt = call_edge.get_call_stmt ();
- update_for_gcall (call_stmt, ctxt, call_edge.get_callee_function ());
-}
-
-/* Extract calling information from the return superedge and update the model
- for the returning call */
-
-void
-region_model::update_for_return_superedge (const return_superedge &return_edge,
- region_model_context *ctxt)
-{
- const gcall &call_stmt = return_edge.get_call_stmt ();
- update_for_return_gcall (call_stmt, ctxt);
-}
-
/* Attempt to use R to replay SUMMARY into this object.
Return true if it is possible. */
return true;
}
-/* Given a true or false edge guarded by conditional statement COND_STMT,
- determine appropriate constraints for the edge to be taken.
-
- If they are feasible, add the constraints and return true.
-
- Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken).
- When returning false, if OUT is non-NULL, write a new rejected_constraint
- to it. */
-
-bool
-region_model::
-apply_constraints_for_gcond (const cfg_superedge &sedge,
- const gcond *cond_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out)
-{
- ::edge cfg_edge = sedge.get_cfg_edge ();
- gcc_assert (cfg_edge != nullptr);
- gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
-
- enum tree_code op = gimple_cond_code (cond_stmt);
- tree lhs = gimple_cond_lhs (cond_stmt);
- tree rhs = gimple_cond_rhs (cond_stmt);
- if (cfg_edge->flags & EDGE_FALSE_VALUE)
- op = invert_tree_comparison (op, false /* honor_nans */);
- return add_constraint (lhs, op, rhs, ctxt, out);
-}
-
-/* Return true iff SWITCH_STMT has a non-default label that contains
- INT_CST. */
-
-static bool
-has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst)
-{
- /* We expect the initial label to be the default; skip it. */
- gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL_TREE);
- unsigned min_idx = 1;
- unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1;
-
- /* Binary search: try to find the label containing INT_CST.
- This requires the cases to be sorted by CASE_LOW (done by the
- gimplifier). */
- while (max_idx >= min_idx)
- {
- unsigned case_idx = (min_idx + max_idx) / 2;
- tree label = gimple_switch_label (switch_stmt, case_idx);
- tree low = CASE_LOW (label);
- gcc_assert (low);
- tree high = CASE_HIGH (label);
- if (!high)
- high = low;
- if (tree_int_cst_compare (int_cst, low) < 0)
- {
- /* INT_CST is below the range of this label. */
- gcc_assert (case_idx > 0);
- max_idx = case_idx - 1;
- }
- else if (tree_int_cst_compare (int_cst, high) > 0)
- {
- /* INT_CST is above the range of this case. */
- min_idx = case_idx + 1;
- }
- else
- /* This case contains INT_CST. */
- return true;
- }
- /* Not found. */
- return false;
-}
-
-/* Return true iff SWITCH_STMT (which must be on an enum value)
- has nondefault cases handling all values in the enum. */
-
-static bool
-has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt,
- tree type)
-{
- gcc_assert (switch_stmt);
- gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE);
-
- for (tree enum_val_iter = TYPE_VALUES (type);
- enum_val_iter;
- enum_val_iter = TREE_CHAIN (enum_val_iter))
- {
- tree enum_val = TREE_VALUE (enum_val_iter);
- gcc_assert (TREE_CODE (enum_val) == CONST_DECL);
- gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST);
- if (!has_nondefault_case_for_value_p (switch_stmt,
- DECL_INITIAL (enum_val)))
- return false;
- }
- return true;
-}
-
-/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
- for the edge to be taken.
-
- If they are feasible, add the constraints and return true.
-
- Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken).
- When returning false, if OUT is non-NULL, write a new rejected_constraint
- to it. */
-
-bool
-region_model::
-apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
- const gswitch *switch_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out)
-{
- tree index = gimple_switch_index (switch_stmt);
- const svalue *index_sval = get_rvalue (index, ctxt);
- bool check_index_type = true;
-
- /* With -fshort-enum, there may be a type cast. */
- if (ctxt && index_sval->get_kind () == SK_UNARYOP
- && TREE_CODE (index_sval->get_type ()) == INTEGER_TYPE)
- {
- const unaryop_svalue *unaryop = as_a <const unaryop_svalue *> (index_sval);
- if (unaryop->get_op () == NOP_EXPR
- && is_a <const initial_svalue *> (unaryop->get_arg ()))
- if (const initial_svalue *initvalop = (as_a <const initial_svalue *>
- (unaryop->get_arg ())))
- if (initvalop->get_type ()
- && TREE_CODE (initvalop->get_type ()) == ENUMERAL_TYPE)
- {
- index_sval = initvalop;
- check_index_type = false;
- }
- }
-
- /* If we're switching based on an enum type, assume that the user is only
- working with values from the enum. Hence if this is an
- implicitly-created "default", assume it doesn't get followed.
- This fixes numerous "uninitialized" false positives where we otherwise
- consider jumping past the initialization cases. */
-
- if (/* Don't check during feasibility-checking (when ctxt is NULL). */
- ctxt
- /* Must be an enum value. */
- && index_sval->get_type ()
- && (!check_index_type
- || TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE)
- && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE
- /* If we have a constant, then we can check it directly. */
- && index_sval->get_kind () != SK_CONSTANT
- && edge.implicitly_created_default_p ()
- && has_nondefault_cases_for_all_enum_values_p (switch_stmt,
- index_sval->get_type ())
- /* Don't do this if there's a chance that the index is
- attacker-controlled. */
- && !ctxt->possibly_tainted_p (index_sval))
- {
- if (out)
- *out = std::make_unique <rejected_default_case> (*this);
- return false;
- }
-
- bounded_ranges_manager *ranges_mgr = get_range_manager ();
- const bounded_ranges *all_cases_ranges
- = ranges_mgr->get_or_create_ranges_for_switch (&edge, switch_stmt);
- bool sat = m_constraints->add_bounded_ranges (index_sval, all_cases_ranges);
- if (!sat && out)
- *out = std::make_unique <rejected_ranges_constraint>
- (*this, index, all_cases_ranges);
- if (sat && ctxt && !all_cases_ranges->empty_p ())
- ctxt->on_bounded_ranges (*index_sval, *all_cases_ranges);
- return sat;
-}
-
-class rejected_eh_dispatch : public rejected_constraint
-{
-public:
- rejected_eh_dispatch (const region_model &model)
- : rejected_constraint (model)
- {}
-
- void dump_to_pp (pretty_printer *pp) const final override
- {
- pp_printf (pp, "rejected_eh_dispatch");
- }
-};
-
-static bool
-exception_matches_type_p (tree exception_type,
- tree catch_type)
-{
- if (catch_type == exception_type)
- return true;
-
- /* TODO (PR analyzer/119697): we should also handle subclasses etc;
- see the rules in https://en.cppreference.com/w/cpp/language/catch
-
- It looks like we should be calling (or emulating)
- can_convert_eh from the C++ FE, but that's specific to the C++ FE. */
-
- return false;
-}
-
-static bool
-matches_any_exception_type_p (eh_catch ehc, tree exception_type)
-{
- if (ehc->type_list == NULL_TREE)
- /* All exceptions are caught here. */
- return true;
-
- for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
- if (exception_matches_type_p (TREE_VALUE (iter),
- exception_type))
- return true;
- return false;
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
- const geh_dispatch *,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out)
-{
- const exception_node *current_node = get_current_thrown_exception ();
- gcc_assert (current_node);
- tree curr_exception_type = current_node->maybe_get_type ();
- if (!curr_exception_type)
- /* We don't know the specific type. */
- return true;
-
- return edge.apply_constraints (this, ctxt, curr_exception_type, out);
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch_try (const eh_dispatch_try_cfg_superedge &edge,
- region_model_context */*ctxt*/,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out)
-{
- /* TODO: can we rely on this ordering?
- or do we need to iterate through prev_catch ? */
- /* The exception must not match any of the previous edges. */
- for (auto sibling_sedge : edge.m_src->m_succs)
- {
- if (sibling_sedge == &edge)
- break;
-
- const eh_dispatch_try_cfg_superedge *sibling_eh_sedge
- = as_a <const eh_dispatch_try_cfg_superedge *> (sibling_sedge);
- if (eh_catch ehc = sibling_eh_sedge->get_eh_catch ())
- if (matches_any_exception_type_p (ehc, exception_type))
- {
- /* The earlier sibling matches, so the "unhandled" edge is
- not taken. */
- if (out)
- *out = std::make_unique<rejected_eh_dispatch> (*this);
- return false;
- }
- }
-
- if (eh_catch ehc = edge.get_eh_catch ())
- {
- /* We have an edge that tried to match one or more types. */
-
- /* The exception must not match any of the previous edges. */
-
- /* It must match this type. */
- if (matches_any_exception_type_p (ehc, exception_type))
- return true;
- else
- {
- /* Exception type doesn't match. */
- if (out)
- *out = std::make_unique<rejected_eh_dispatch> (*this);
- return false;
- }
- }
- else
- {
- /* This is the "unhandled exception" edge.
- If we get here then no sibling edges matched;
- we will follow this edge. */
- return true;
- }
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch_allowed (const eh_dispatch_allowed_cfg_superedge &edge,
- region_model_context */*ctxt*/,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out)
-{
- auto curr_thrown_exception_node = get_current_thrown_exception ();
- gcc_assert (curr_thrown_exception_node);
- tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
- eh_region eh_reg = edge.get_eh_region ();
- tree type_list = eh_reg->u.allowed.type_list;
-
- switch (edge.get_eh_kind ())
- {
- default:
- gcc_unreachable ();
- case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
- if (!curr_exception_type)
- {
- /* We don't know the specific type;
- assume we have one of an expected type. */
- return true;
- }
- for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
- if (exception_matches_type_p (TREE_VALUE (iter),
- exception_type))
- return true;
- if (out)
- *out = std::make_unique<rejected_eh_dispatch> (*this);
- return false;
-
- case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
- if (!curr_exception_type)
- {
- /* We don't know the specific type;
- assume we don't have one of an expected type. */
- if (out)
- *out = std::make_unique<rejected_eh_dispatch> (*this);
- return false;
- }
- for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
- if (exception_matches_type_p (TREE_VALUE (iter),
- exception_type))
- {
- if (out)
- *out = std::make_unique<rejected_eh_dispatch> (*this);
- return false;
- }
- return true;
- }
-}
-
-/* Given an edge reached by GOTO_STMT, determine appropriate constraints
- for the edge to be taken.
-
- If they are feasible, add the constraints and return true.
-
- Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken). */
-
-bool
-region_model::apply_constraints_for_ggoto (const cfg_superedge &edge,
- const ggoto *goto_stmt,
- region_model_context *ctxt)
-{
- tree dest = gimple_goto_dest (goto_stmt);
- const svalue *dest_sval = get_rvalue (dest, ctxt);
-
- /* If we know we were jumping to a specific label. */
- if (tree dst_label = edge.m_dest->get_label ())
- {
- const label_region *dst_label_reg
- = m_mgr->get_region_for_label (dst_label);
- const svalue *dst_label_ptr
- = m_mgr->get_ptr_svalue (ptr_type_node, dst_label_reg);
-
- if (!add_constraint (dest_sval, EQ_EXPR, dst_label_ptr, ctxt))
- return false;
- }
-
- return true;
-}
-
/* For use with push_frame when handling a top-level call within the analysis.
PARAM has a defined but unknown initial value.
Anything it points to has escaped, since the calling context "knows"
m_call_stmt (call_stmt),
m_caller_frame (caller_frame)
{}
- bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder) override
+
+ pending_location
+ get_pending_location_for_diag () const override
{
- if (m_inner && custom_finder == nullptr)
- {
- /* Custom stmt_finder to use m_call_stmt for the
- diagnostic. */
- class my_finder : public stmt_finder
- {
- public:
- my_finder (const gcall *call_stmt,
- const frame_region &caller_frame)
- : m_call_stmt (call_stmt),
- m_caller_frame (caller_frame)
- {}
- std::unique_ptr<stmt_finder> clone () const override
- {
- return std::make_unique<my_finder> (m_call_stmt, m_caller_frame);
- }
- const gimple *find_stmt (const exploded_path &) override
- {
- return m_call_stmt;
- }
- void update_event_loc_info (event_loc_info &loc_info) final override
- {
- loc_info.m_fndecl = m_caller_frame.get_fndecl ();
- loc_info.m_depth = m_caller_frame.get_stack_depth ();
- }
+ pending_location ploc
+ = region_model_context_decorator::get_pending_location_for_diag ();
- private:
- const gcall *m_call_stmt;
- const frame_region &m_caller_frame;
- };
- my_finder finder (m_call_stmt, m_caller_frame);
- return m_inner->warn (std::move (d), &finder);
- }
- else
- return region_model_context_decorator::warn (std::move (d),
- custom_finder);
+ ploc.m_event_loc_info
+ = event_loc_info (m_call_stmt->location,
+ m_caller_frame.get_fndecl (),
+ m_caller_frame.get_stack_depth ());
+
+ return ploc;
}
+
const gimple *get_stmt () const override
{
return m_call_stmt;
if (update_state_machine && cd)
{
- const svalue *ptr_sval
- = m_mgr->get_ptr_svalue (cd->get_lhs_type (), reg);
- transition_ptr_sval_non_null (ctxt, ptr_sval);
+ const svalue *ptr_sval
+ = m_mgr->get_ptr_svalue (cd->get_lhs_type (), reg);
+ transition_ptr_sval_non_null (ctxt, ptr_sval);
}
return reg;
set_value (errno_reg, new_errno_sval, cd.get_ctxt ());
}
+// class region_model_context
+
+bool
+region_model_context::
+warn (std::unique_ptr<pending_diagnostic> d,
+ std::unique_ptr<pending_location::fixer_for_epath> ploc_fixer)
+{
+ pending_location ploc (get_pending_location_for_diag ());
+ ploc.m_fixer_for_epath = std::move (ploc_fixer);
+ return warn_at (std::move (d), std::move (ploc));
+}
+
/* class noop_region_model_context : public region_model_context. */
void
/* engine's ctor. */
-engine::engine (const supergraph *sg, logger *logger)
-: m_sg (sg), m_mgr (logger)
+engine::engine (region_model_manager &mgr,
+ const supergraph *sg,
+ logger *logger)
+: m_mgr (mgr),
+ m_sg (sg)
{
}
ASSERT_EQ (merged_p_star_reg, merged.get_lvalue (y, nullptr));
}
- /* Pointers: non-NULL ptrs to different globals: should be unknown. */
+ /* Pointers: non-NULL ptrs to different globals should not merge;
+ see e.g. gcc.dg/analyzer/torture/uninit-pr108725.c */
{
- region_model merged (&mgr);
+ region_model merged_model (&mgr);
+ program_point point (program_point::origin (mgr));
+ test_region_model_context ctxt;
/* x == &y vs x == &z in the input models; these are actually casts
of the ptrs to "int". */
- const svalue *merged_x_sval;
- // TODO:
- assert_region_models_merge (x, addr_of_y, addr_of_z, &merged,
- &merged_x_sval);
-
- /* We should get x == unknown in the merged model. */
- ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+ region_model model0 (&mgr);
+ region_model model1 (&mgr);
+ model0.set_value (model0.get_lvalue (x, &ctxt),
+ model0.get_rvalue (addr_of_y, &ctxt),
+ &ctxt);
+ model1.set_value (model1.get_lvalue (x, &ctxt),
+ model1.get_rvalue (addr_of_z, &ctxt),
+ &ctxt);
+ /* They should not be mergeable. */
+ ASSERT_FALSE (model0.can_merge_with_p (model1, point, &merged_model));
}
/* Pointers: non-NULL and non-NULL: ptr to a heap region. */
test_widening_constraints ()
{
region_model_manager mgr;
- function_point point (program_point::origin (mgr).get_function_point ());
+ const supernode *snode = nullptr;
tree int_0 = integer_zero_node;
tree int_m1 = build_int_cst (integer_type_node, -1);
tree int_1 = integer_one_node;
const svalue *int_0_sval = mgr.get_or_create_constant_svalue (int_0);
const svalue *int_1_sval = mgr.get_or_create_constant_svalue (int_1);
const svalue *w_zero_then_one_sval
- = mgr.get_or_create_widening_svalue (integer_type_node, point,
+ = mgr.get_or_create_widening_svalue (integer_type_node, snode,
int_0_sval, int_1_sval);
const widening_svalue *w_zero_then_one
= w_zero_then_one_sval->dyn_cast_widening_svalue ();
#include "analyzer/known-function-manager.h"
#include "analyzer/region-model-manager.h"
#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
#include "text-art/widget.h"
#include "text-art/dump.h"
const uncertainty_t *uncertainty);
void on_return (const greturn *stmt, region_model_context *ctxt);
- void on_setjmp (const gcall &stmt, const exploded_node *enode,
+ void on_setjmp (const gcall &stmt,
+ const exploded_node &enode,
+ const superedge &sedge,
region_model_context *ctxt);
void on_longjmp (const gcall &longjmp_call, const gcall &setjmp_call,
int setjmp_stack_depth, region_model_context *ctxt);
- void update_for_phis (const supernode *snode,
- const cfg_superedge *last_cfg_superedge,
- region_model_context *ctxt);
-
- void handle_phi (const gphi *phi, tree lhs, tree rhs,
- const region_model &old_state,
- hash_set<const svalue *> &svals_changing_meaning,
- region_model_context *ctxt);
-
- bool maybe_update_for_edge (const superedge &edge,
- const gimple *last_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out);
-
void update_for_gcall (const gcall &call_stmt,
region_model_context *ctxt,
function *callee = nullptr);
return retval;
}
- bool
- apply_constraints_for_eh_dispatch_try
- (const eh_dispatch_try_cfg_superedge &edge,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out);
-
- bool
- apply_constraints_for_eh_dispatch_allowed
- (const eh_dispatch_allowed_cfg_superedge &edge,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out);
-
private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
bool *out,
region_model_context *ctxt);
- void update_for_call_superedge (const call_superedge &call_edge,
- region_model_context *ctxt);
- void update_for_return_superedge (const return_superedge &return_edge,
- region_model_context *ctxt);
- bool apply_constraints_for_gcond (const cfg_superedge &edge,
- const gcond *cond_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out);
- bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
- const gswitch *switch_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out);
- bool apply_constraints_for_ggoto (const cfg_superedge &edge,
- const ggoto *goto_stmt,
- region_model_context *ctxt);
-
- bool
- apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
- const geh_dispatch *eh_dispatch_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out);
-
void poison_any_pointers_to_descendents (const region *reg,
enum poison_kind pkind);
class region_model_context
{
public:
+ bool
+ warn (std::unique_ptr<pending_diagnostic> d,
+ std::unique_ptr<pending_location::fixer_for_epath> ploc_fixer = nullptr);
+
+ /* Hook for determining where diagnostics are to currently be emitted. */
+ virtual pending_location
+ get_pending_location_for_diag () const = 0;
+
/* Hook for clients to store pending diagnostics.
- Return true if the diagnostic was stored, or false if it was deleted.
- Optionally provide a custom stmt_finder. */
- virtual bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder = nullptr) = 0;
+ Return true if the diagnostic was stored, or false if it was deleted. */
+ virtual bool
+ warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&ploc) = 0;
/* Hook for clients to add a note to the last previously stored
pending diagnostic. */
class noop_region_model_context : public region_model_context
{
public:
- bool warn (std::unique_ptr<pending_diagnostic>,
- const stmt_finder *) override { return false; }
+ pending_location
+ get_pending_location_for_diag () const override
+ {
+ return pending_location ();
+ }
+ bool
+ warn_at (std::unique_ptr<pending_diagnostic>,
+ pending_location &&) override
+ {
+ return false;
+ }
void add_note (std::unique_ptr<pending_note>) override;
void add_event (std::unique_ptr<checker_event>) override;
void on_svalue_leak (const svalue *) override {}
class region_model_context_decorator : public region_model_context
{
public:
- bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder) override
+ pending_location
+ get_pending_location_for_diag () const override
{
if (m_inner)
- return m_inner->warn (std::move (d), custom_finder);
+ return m_inner->get_pending_location_for_diag ();
+ else
+ return pending_location ();
+ }
+
+ bool
+ warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&ploc) override
+ {
+ if (m_inner)
+ return m_inner->warn_at (std::move (d), std::move (ploc));
else
return false;
}
class annotating_context : public region_model_context_decorator
{
public:
- bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *custom_finder) override
+ bool
+ warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&ploc) override
{
if (m_inner)
- if (m_inner->warn (std::move (d), custom_finder))
+ if (m_inner->warn_at (std::move (d), std::move (ploc)))
{
add_annotations ();
return true;
}
bool mergeable_svalue_p (const svalue *) const;
- const function_point &get_function_point () const
+
+ const supernode *get_supernode () const
{
- return m_point.get_function_point ();
+ return m_point.get_supernode ();
}
void on_widening_reuse (const widening_svalue *widening_sval);
class engine
{
public:
- engine (const supergraph *sg = nullptr, logger *logger = nullptr);
+ engine (region_model_manager &mgr,
+ const supergraph *sg = nullptr,
+ logger *logger = nullptr);
const supergraph *get_supergraph () { return m_sg; }
region_model_manager *get_model_manager () { return &m_mgr; }
known_function_manager *get_known_function_manager ()
void log_stats (logger *logger) const;
private:
+ region_model_manager &m_mgr;
const supergraph *m_sg;
- region_model_manager m_mgr;
};
} // namespace ana
class test_region_model_context : public noop_region_model_context
{
public:
- bool warn (std::unique_ptr<pending_diagnostic> d,
- const stmt_finder *) final override
+ bool
+ warn_at (std::unique_ptr<pending_diagnostic> d,
+ pending_location &&) final override
{
m_diagnostics.safe_push (d.release ());
return true;
= ext_state->get_engine ()->get_supergraph ())
{
const gimple *def_stmt = SSA_NAME_DEF_STMT (expr);
- const supernode *snode
- = sg->get_supernode_for_stmt (def_stmt);
- gcc_assert (snode->get_function () == &m_fun);
+ if (gimple_code (def_stmt) != GIMPLE_PHI)
+ {
+ const supernode *snode
+ = sg->get_supernode_for_stmt (def_stmt);
+ gcc_assert (snode->get_function () == &m_fun);
+ }
}
}
break;
return m_start;
}
- bool on_stmt (sm_context &sm_ctxt, const supernode *node,
+ bool on_stmt (sm_context &sm_ctxt,
const gimple *stmt) const final override;
- void on_condition (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const svalue *lhs, const tree_code op,
+ void on_condition (sm_context &sm_ctxt,
+ const svalue *lhs, const tree_code op,
const svalue *rhs) const final override;
bool can_purge_p (state_t s) const final override;
tree m_SOCK_DGRAM;
private:
- void on_open (sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ void on_open (sm_context &sm_ctxt,
const gcall &call) const;
- void on_creat (sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ void on_creat (sm_context &sm_ctxt,
const gcall &call) const;
- void on_close (sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ void on_close (sm_context &sm_ctxt,
const gcall &call) const;
- void on_read (sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ void on_read (sm_context &sm_ctxt,
const gcall &call, const tree callee_fndecl) const;
- void on_write (sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ void on_write (sm_context &sm_ctxt,
const gcall &call, const tree callee_fndecl) const;
- void check_for_open_fd (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call,
+ void check_for_open_fd (sm_context &sm_ctxt,
+ const gcall &call,
const tree callee_fndecl,
enum access_directions access_fn) const;
void make_valid_transitions_on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs) const;
void make_invalid_transitions_on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs) const;
- void check_for_fd_attrs (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call,
- const tree callee_fndecl, const char *attr_name,
+ void check_for_fd_attrs (sm_context &sm_ctxt,
+ const gcall &call,
+ const tree callee_fndecl,
+ const char *attr_name,
access_directions fd_attr_access_dir) const;
- void check_for_dup (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call, const tree callee_fndecl,
- enum dup kind) const;
+ void check_for_dup (sm_context &sm_ctxt,
+ const gcall &call,
+ const tree callee_fndecl,
+ enum dup kind) const;
state_t get_state_for_socket_type (const svalue *socket_type_sval) const;
bool successful,
sm_context &sm_ctxt,
const svalue *fd_sval,
- const supernode *node,
state_t old_state,
bool *complained = nullptr) const;
bool check_for_new_socket_fd (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
const svalue *fd_sval,
- const supernode *node,
state_t old_state,
enum expected_phase expected_phase) const;
};
}
bool
-fd_state_machine::on_stmt (sm_context &sm_ctxt, const supernode *node,
+fd_state_machine::on_stmt (sm_context &sm_ctxt,
const gimple *stmt) const
{
if (const gcall *call = dyn_cast<const gcall *> (stmt))
{
if (is_named_call_p (callee_fndecl, "open", *call, 2))
{
- on_open (sm_ctxt, node, stmt, *call);
+ on_open (sm_ctxt, *call);
return true;
} // "open"
if (is_named_call_p (callee_fndecl, "creat", *call, 2))
{
- on_creat (sm_ctxt, node, stmt, *call);
+ on_creat (sm_ctxt, *call);
return true;
} // "creat"
if (is_named_call_p (callee_fndecl, "close", *call, 1))
{
- on_close (sm_ctxt, node, stmt, *call);
+ on_close (sm_ctxt, *call);
return true;
} // "close"
if (is_named_call_p (callee_fndecl, "write", *call, 3))
{
- on_write (sm_ctxt, node, stmt, *call, callee_fndecl);
+ on_write (sm_ctxt, *call, callee_fndecl);
return true;
} // "write"
if (is_named_call_p (callee_fndecl, "read", *call, 3))
{
- on_read (sm_ctxt, node, stmt, *call, callee_fndecl);
+ on_read (sm_ctxt, *call, callee_fndecl);
return true;
} // "read"
if (is_named_call_p (callee_fndecl, "dup", *call, 1))
{
- check_for_dup (sm_ctxt, node, stmt, *call, callee_fndecl, DUP_1);
+ check_for_dup (sm_ctxt, *call, callee_fndecl, DUP_1);
return true;
}
if (is_named_call_p (callee_fndecl, "dup2", *call, 2))
{
- check_for_dup (sm_ctxt, node, stmt, *call, callee_fndecl, DUP_2);
+ check_for_dup (sm_ctxt, *call, callee_fndecl, DUP_2);
return true;
}
if (is_named_call_p (callee_fndecl, "dup3", *call, 3))
{
- check_for_dup (sm_ctxt, node, stmt, *call, callee_fndecl, DUP_3);
+ check_for_dup (sm_ctxt, *call, callee_fndecl, DUP_3);
return true;
}
{
// Handle __attribute__((fd_arg))
- check_for_fd_attrs (sm_ctxt, node, stmt, *call, callee_fndecl,
+ check_for_fd_attrs (sm_ctxt, *call, callee_fndecl,
"fd_arg", DIRS_READ_WRITE);
// Handle __attribute__((fd_arg_read))
- check_for_fd_attrs (sm_ctxt, node, stmt, *call, callee_fndecl,
+ check_for_fd_attrs (sm_ctxt, *call, callee_fndecl,
"fd_arg_read", DIRS_READ);
// Handle __attribute__((fd_arg_write))
- check_for_fd_attrs (sm_ctxt, node, stmt, *call, callee_fndecl,
+ check_for_fd_attrs (sm_ctxt, *call, callee_fndecl,
"fd_arg_write", DIRS_WRITE);
}
}
void
fd_state_machine::check_for_fd_attrs (
- sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ sm_context &sm_ctxt,
const gcall &call, const tree callee_fndecl, const char *attr_name,
access_directions fd_attr_access_dir) const
{
{
tree arg = gimple_call_arg (&call, arg_idx);
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- state_t state = sm_ctxt.get_state (stmt, arg);
+ state_t state = sm_ctxt.get_state (arg);
bool bit_set = bitmap_bit_p (argmap, arg_idx);
if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
continue;
if (is_closed_fd_p (state))
{
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_use_after_close>
(*this, diag_arg,
fndecl, attr_name,
{
if (!is_constant_fd_p (state))
{
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_use_without_check>
(*this, diag_arg,
fndecl, attr_name,
if (is_writeonly_fd_p (state))
{
sm_ctxt.warn
- (node, stmt, arg,
+ (arg,
std::make_unique<fd_access_mode_mismatch> (*this, diag_arg,
DIRS_WRITE,
fndecl,
if (is_readonly_fd_p (state))
{
sm_ctxt.warn
- (node, stmt, arg,
+ (arg,
std::make_unique<fd_access_mode_mismatch> (*this, diag_arg,
DIRS_READ,
fndecl,
void
-fd_state_machine::on_open (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call) const
+fd_state_machine::on_open (sm_context &sm_ctxt, const gcall &call) const
{
tree lhs = gimple_call_lhs (&call);
if (lhs)
switch (mode)
{
case READ_ONLY:
- sm_ctxt.on_transition (node, stmt, lhs, m_start,
+ sm_ctxt.on_transition (lhs, m_start,
m_unchecked_read_only);
break;
case WRITE_ONLY:
- sm_ctxt.on_transition (node, stmt, lhs, m_start,
+ sm_ctxt.on_transition (lhs, m_start,
m_unchecked_write_only);
break;
default:
- sm_ctxt.on_transition (node, stmt, lhs, m_start,
+ sm_ctxt.on_transition (lhs, m_start,
m_unchecked_read_write);
}
}
else
{
- sm_ctxt.warn (node, stmt, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
}
void
-fd_state_machine::on_creat (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call) const
+fd_state_machine::on_creat (sm_context &sm_ctxt, const gcall &call) const
{
tree lhs = gimple_call_lhs (&call);
if (lhs)
- sm_ctxt.on_transition (node, stmt, lhs, m_start, m_unchecked_write_only);
+ sm_ctxt.on_transition (lhs, m_start, m_unchecked_write_only);
else
- sm_ctxt.warn (node, stmt, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
void
-fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call,
+fd_state_machine::check_for_dup (sm_context &sm_ctxt, const gcall &call,
const tree callee_fndecl, enum dup kind) const
{
tree lhs = gimple_call_lhs (&call);
tree arg_1 = gimple_call_arg (&call, 0);
- state_t state_arg_1 = sm_ctxt.get_state (stmt, arg_1);
+ state_t state_arg_1 = sm_ctxt.get_state (arg_1);
if (state_arg_1 == m_stop)
return;
if (!(is_constant_fd_p (state_arg_1) || is_valid_fd_p (state_arg_1)
|| state_arg_1 == m_start))
{
- check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl,
- DIRS_READ_WRITE);
+ check_for_open_fd (sm_ctxt, call, callee_fndecl, DIRS_READ_WRITE);
return;
}
switch (kind)
if (lhs)
{
if (is_constant_fd_p (state_arg_1) || state_arg_1 == m_start)
- sm_ctxt.set_next_state (stmt, lhs, m_unchecked_read_write);
+ sm_ctxt.set_next_state (lhs, m_unchecked_read_write);
else
- sm_ctxt.set_next_state (stmt, lhs,
+ sm_ctxt.set_next_state (lhs,
valid_to_unchecked_state (state_arg_1));
}
break;
case DUP_2:
case DUP_3:
tree arg_2 = gimple_call_arg (&call, 1);
- state_t state_arg_2 = sm_ctxt.get_state (stmt, arg_2);
+ state_t state_arg_2 = sm_ctxt.get_state (arg_2);
tree diag_arg_2 = sm_ctxt.get_diagnostic_tree (arg_2);
if (state_arg_2 == m_stop)
return;
if (!(is_constant_fd_p (state_arg_2) || is_valid_fd_p (state_arg_2)
|| state_arg_2 == m_start))
{
- sm_ctxt.warn (
- node, stmt, arg_2,
- std::make_unique<fd_use_without_check> (*this, diag_arg_2,
- callee_fndecl));
+ sm_ctxt.warn
+ (arg_2,
+ std::make_unique<fd_use_without_check> (*this, diag_arg_2,
+ callee_fndecl));
return;
}
/* dup2 returns value of its second argument on success.But, the
if (lhs)
{
if (is_constant_fd_p (state_arg_1) || state_arg_1 == m_start)
- sm_ctxt.set_next_state (stmt, lhs, m_unchecked_read_write);
+ sm_ctxt.set_next_state (lhs, m_unchecked_read_write);
else
- sm_ctxt.set_next_state (stmt, lhs,
+ sm_ctxt.set_next_state (lhs,
valid_to_unchecked_state (state_arg_1));
}
}
void
-fd_state_machine::on_close (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call) const
+fd_state_machine::on_close (sm_context &sm_ctxt, const gcall &call) const
{
tree arg = gimple_call_arg (&call, 0);
- state_t state = sm_ctxt.get_state (stmt, arg);
+ state_t state = sm_ctxt.get_state (arg);
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.on_transition (node, stmt, arg, m_start, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_unchecked_read_write, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_unchecked_read_only, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_unchecked_write_only, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_valid_read_write, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_valid_read_only, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_valid_write_only, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_constant_fd, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_new_datagram_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_new_stream_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_new_unknown_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_bound_datagram_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_bound_stream_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_bound_unknown_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_listening_stream_socket, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_connected_stream_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_start, m_closed);
+ sm_ctxt.on_transition (arg, m_unchecked_read_write, m_closed);
+ sm_ctxt.on_transition (arg, m_unchecked_read_only, m_closed);
+ sm_ctxt.on_transition (arg, m_unchecked_write_only, m_closed);
+ sm_ctxt.on_transition (arg, m_valid_read_write, m_closed);
+ sm_ctxt.on_transition (arg, m_valid_read_only, m_closed);
+ sm_ctxt.on_transition (arg, m_valid_write_only, m_closed);
+ sm_ctxt.on_transition (arg, m_constant_fd, m_closed);
+ sm_ctxt.on_transition (arg, m_new_datagram_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_new_stream_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_new_unknown_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_bound_datagram_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_bound_stream_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_bound_unknown_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_listening_stream_socket, m_closed);
+ sm_ctxt.on_transition (arg, m_connected_stream_socket, m_closed);
if (is_closed_fd_p (state))
{
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_double_close> (*this, diag_arg));
- sm_ctxt.set_next_state (stmt, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
}
void
-fd_state_machine::on_read (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call,
+fd_state_machine::on_read (sm_context &sm_ctxt, const gcall &call,
const tree callee_fndecl) const
{
- check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIRS_READ);
+ check_for_open_fd (sm_ctxt,call, callee_fndecl, DIRS_READ);
}
void
-fd_state_machine::on_write (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall &call,
+fd_state_machine::on_write (sm_context &sm_ctxt, const gcall &call,
const tree callee_fndecl) const
{
- check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIRS_WRITE);
+ check_for_open_fd (sm_ctxt,call, callee_fndecl, DIRS_WRITE);
}
void
fd_state_machine::check_for_open_fd (
- sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+ sm_context &sm_ctxt,
const gcall &call, const tree callee_fndecl,
enum access_directions callee_fndecl_dir) const
{
tree arg = gimple_call_arg (&call, 0);
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- state_t state = sm_ctxt.get_state (stmt, arg);
+ state_t state = sm_ctxt.get_state (arg);
if (is_closed_fd_p (state))
{
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_use_after_close> (*this, diag_arg,
callee_fndecl));
}
|| state == m_listening_stream_socket)
/* Complain about fncall on socket in wrong phase. */
sm_ctxt.warn
- (node, stmt, arg,
+ (arg,
std::make_unique<fd_phase_mismatch> (*this, diag_arg,
callee_fndecl,
state,
|| state == m_stop))
{
if (!is_constant_fd_p (state))
- sm_ctxt.warn (
- node, stmt, arg,
- std::make_unique<fd_use_without_check> (*this, diag_arg,
- callee_fndecl));
+ sm_ctxt.warn
+ (arg,
+ std::make_unique<fd_use_without_check> (*this, diag_arg,
+ callee_fndecl));
}
switch (callee_fndecl_dir)
{
if (is_writeonly_fd_p (state))
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_access_mode_mismatch>
(*this, diag_arg, DIRS_WRITE, callee_fndecl));
}
if (is_readonly_fd_p (state))
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<fd_access_mode_mismatch>
(*this, diag_arg, DIRS_READ, callee_fndecl));
}
fd_state_machine::on_socket (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
- const extrinsic_state &ext_state) const
+ const extrinsic_state &) const
{
const gcall &call = cd.get_call_stmt ();
- engine *eng = ext_state.get_engine ();
- const supergraph *sg = eng->get_supergraph ();
- const supernode *node = sg->get_supernode_for_stmt (&call);
region_model *model = cd.get_model ();
if (successful)
const svalue *socket_type_sval = cd.get_arg_svalue (1);
state_machine::state_t new_state
= get_state_for_socket_type (socket_type_sval);
- sm_ctxt.on_transition (node, &call, new_fd, m_start, new_state);
+ sm_ctxt.on_transition (new_fd, m_start, new_state);
model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
}
else
- sm_ctxt.warn (node, &call, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
else
bool successful,
sm_context &sm_ctxt,
const svalue *fd_sval,
- const supernode *node,
state_t old_state,
bool *complained) const
{
- const gcall &call = cd.get_call_stmt ();
-
if (is_closed_fd_p (old_state))
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_use_after_close> (*this, diag_arg,
cd.get_fndecl_for_call ()));
if (complained)
/* Complain about non-socket. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_type_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_use_without_check> (*this, diag_arg,
cd.get_fndecl_for_call ()));
if (complained)
bool successful,
sm_context &sm_ctxt,
const svalue *fd_sval,
- const supernode *node,
state_t old_state,
enum expected_phase expected_phase)
const
model->get_store_value (sized_address_reg, cd.get_ctxt ());
if (!check_for_socket_fd (cd, successful, sm_ctxt,
- fd_sval, node, old_state, &complained))
+ fd_sval, old_state, &complained))
return false;
else if (!complained
&& !(old_state == m_new_stream_socket
/* Complain about "bind" or "connect" in wrong phase. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
sm_ctxt.warn
- (node, &cd.get_call_stmt (), fd_sval,
+ (fd_sval,
std::make_unique<fd_phase_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
{
/* If we were in the start state, assume we had a new socket. */
if (old_state == m_start)
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval,
- m_new_unknown_socket);
+ sm_ctxt.set_next_state (fd_sval, m_new_unknown_socket);
}
/* Passing NULL as the address will lead to failure. */
fd_state_machine::on_bind (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
- const extrinsic_state &ext_state) const
+ const extrinsic_state &) const
{
- const gcall &call = cd.get_call_stmt ();
- engine *eng = ext_state.get_engine ();
- const supergraph *sg = eng->get_supergraph ();
- const supernode *node = sg->get_supernode_for_stmt (&call);
const svalue *fd_sval = cd.get_arg_svalue (0);
region_model *model = cd.get_model ();
- state_t old_state = sm_ctxt.get_state (&call, fd_sval);
+ state_t old_state = sm_ctxt.get_state (fd_sval);
if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
- fd_sval, node, old_state,
+ fd_sval, old_state,
EXPECTED_PHASE_CAN_BIND))
return false;
next_state = m_stop;
else
gcc_unreachable ();
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval, next_state);
+ sm_ctxt.set_next_state (fd_sval, next_state);
model->update_for_zero_return (cd, true);
}
else
fd_state_machine::on_listen (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
- const extrinsic_state &ext_state) const
+ const extrinsic_state &) const
{
- const gcall &call = cd.get_call_stmt ();
- engine *eng = ext_state.get_engine ();
- const supergraph *sg = eng->get_supergraph ();
- const supernode *node = sg->get_supernode_for_stmt (&cd.get_call_stmt ());
const svalue *fd_sval = cd.get_arg_svalue (0);
region_model *model = cd.get_model ();
- state_t old_state = sm_ctxt.get_state (&call, fd_sval);
+ state_t old_state = sm_ctxt.get_state (fd_sval);
/* We expect a stream socket that's had "bind" called on it. */
- if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, old_state))
return false;
if (!(old_state == m_start
|| old_state == m_constant_fd
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
if (is_stream_socket_fd_p (old_state))
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_phase_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
EXPECTED_PHASE_CAN_LISTEN));
else
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_type_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
if (successful)
{
model->update_for_zero_return (cd, true);
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval,
- m_listening_stream_socket);
+ sm_ctxt.set_next_state (fd_sval, m_listening_stream_socket);
}
else
{
model->update_for_int_cst_return (cd, -1, true);
model->set_errno (cd);
if (old_state == m_start)
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval,
- m_bound_stream_socket);
+ sm_ctxt.set_next_state (fd_sval, m_bound_stream_socket);
}
return true;
fd_state_machine::on_accept (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
- const extrinsic_state &ext_state) const
+ const extrinsic_state &) const
{
const gcall &call = cd.get_call_stmt ();
- engine *eng = ext_state.get_engine ();
- const supergraph *sg = eng->get_supergraph ();
- const supernode *node = sg->get_supernode_for_stmt (&call);
const svalue *fd_sval = cd.get_arg_svalue (0);
const svalue *address_sval = cd.get_arg_svalue (1);
const svalue *len_ptr_sval = cd.get_arg_svalue (2);
region_model *model = cd.get_model ();
- state_t old_state = sm_ctxt.get_state (&call, fd_sval);
+ state_t old_state = sm_ctxt.get_state (fd_sval);
if (!address_sval->all_zeroes_p ())
{
}
/* We expect a stream socket in the "listening" state. */
- if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, old_state))
return false;
if (old_state == m_start || old_state == m_constant_fd)
/* If we were in the start state (or a constant), assume we had the
expected state. */
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval,
- m_listening_stream_socket);
+ sm_ctxt.set_next_state (fd_sval, m_listening_stream_socket);
else if (old_state == m_stop)
{
/* No further complaints. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval);
if (is_stream_socket_fd_p (old_state))
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_phase_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
EXPECTED_PHASE_CAN_ACCEPT));
else
sm_ctxt.warn
- (node, &call, fd_sval,
+ (fd_sval,
std::make_unique<fd_type_mismatch> (*this, diag_arg,
cd.get_fndecl_for_call (),
old_state,
p);
if (!add_constraint_ge_zero (model, new_fd, cd.get_ctxt ()))
return false;
- sm_ctxt.on_transition (node, &call, new_fd,
- m_start, m_connected_stream_socket);
+ sm_ctxt.on_transition (new_fd, m_start, m_connected_stream_socket);
model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
}
else
- sm_ctxt.warn (node, &call, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
else
fd_state_machine::on_connect (const call_details &cd,
bool successful,
sm_context &sm_ctxt,
- const extrinsic_state &ext_state) const
+ const extrinsic_state &) const
{
- const gcall &call = cd.get_call_stmt ();
- engine *eng = ext_state.get_engine ();
- const supergraph *sg = eng->get_supergraph ();
- const supernode *node = sg->get_supernode_for_stmt (&call);
const svalue *fd_sval = cd.get_arg_svalue (0);
region_model *model = cd.get_model ();
- state_t old_state = sm_ctxt.get_state (&call, fd_sval);
+ state_t old_state = sm_ctxt.get_state (fd_sval);
if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
- fd_sval, node, old_state,
+ fd_sval, old_state,
EXPECTED_PHASE_CAN_CONNECT))
return false;
next_state = m_stop;
else
gcc_unreachable ();
- sm_ctxt.set_next_state (&cd.get_call_stmt (), fd_sval, next_state);
+ sm_ctxt.set_next_state (fd_sval, next_state);
}
else
{
}
void
-fd_state_machine::on_condition (sm_context &sm_ctxt, const supernode *node,
- const gimple *stmt, const svalue *lhs,
- enum tree_code op, const svalue *rhs) const
+fd_state_machine::on_condition (sm_context &sm_ctxt,
+ const svalue *lhs,
+ enum tree_code op,
+ const svalue *rhs) const
{
if (tree cst = rhs->maybe_get_constant ())
{
if (val == -1)
{
if (op == NE_EXPR)
- make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+ make_valid_transitions_on_condition (sm_ctxt, lhs);
else if (op == EQ_EXPR)
- make_invalid_transitions_on_condition (sm_ctxt, node, stmt,
- lhs);
+ make_invalid_transitions_on_condition (sm_ctxt, lhs);
}
}
}
if (rhs->all_zeroes_p ())
{
if (op == GE_EXPR)
- make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+ make_valid_transitions_on_condition (sm_ctxt, lhs);
else if (op == LT_EXPR)
- make_invalid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+ make_invalid_transitions_on_condition (sm_ctxt, lhs);
}
}
void
fd_state_machine::make_valid_transitions_on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs) const
{
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_read_write,
+ sm_ctxt.on_transition (lhs, m_unchecked_read_write,
m_valid_read_write);
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_read_only,
+ sm_ctxt.on_transition (lhs, m_unchecked_read_only,
m_valid_read_only);
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_write_only,
+ sm_ctxt.on_transition (lhs, m_unchecked_write_only,
m_valid_write_only);
}
void
-fd_state_machine::make_invalid_transitions_on_condition (
- sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
- const svalue *lhs) const
+fd_state_machine::
+make_invalid_transitions_on_condition (sm_context &sm_ctxt,
+ const svalue *lhs) const
{
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_read_write, m_invalid);
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_read_only, m_invalid);
- sm_ctxt.on_transition (node, stmt, lhs, m_unchecked_write_only, m_invalid);
+ sm_ctxt.on_transition (lhs, m_unchecked_read_write, m_invalid);
+ sm_ctxt.on_transition (lhs, m_unchecked_read_only, m_invalid);
+ sm_ctxt.on_transition (lhs, m_unchecked_write_only, m_invalid);
}
bool
return true;
const svalue *fd_sval = cd.get_arg_svalue (0);
- state_machine::state_t old_state
- = sm_ctxt->get_state (&cd.get_call_stmt (), fd_sval);
+ state_machine::state_t old_state = sm_ctxt->get_state (fd_sval);
if (fd_sm->is_closed_fd_p (old_state)
|| old_state == fd_sm->m_invalid)
}
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
void on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const final override;
bool
fileptr_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
if (const gcall *call = dyn_cast <const gcall *> (stmt))
{
tree lhs = gimple_call_lhs (call);
if (lhs)
- sm_ctxt.on_transition (node, stmt, lhs, m_start, m_unchecked);
+ sm_ctxt.on_transition (lhs, m_start, m_unchecked);
else
{
/* TODO: report leak. */
{
tree arg = gimple_call_arg (call, 0);
- sm_ctxt.on_transition (node, stmt, arg, m_start, m_closed);
+ sm_ctxt.on_transition (arg, m_start, m_closed);
// TODO: is it safe to call fclose (NULL) ?
- sm_ctxt.on_transition (node, stmt, arg, m_unchecked, m_closed);
- sm_ctxt.on_transition (node, stmt, arg, m_null, m_closed);
+ sm_ctxt.on_transition (arg, m_unchecked, m_closed);
+ sm_ctxt.on_transition (arg, m_null, m_closed);
- sm_ctxt.on_transition (node, stmt , arg, m_nonnull, m_closed);
+ sm_ctxt.on_transition (arg, m_nonnull, m_closed);
- if (sm_ctxt.get_state (stmt, arg) == m_closed)
+ if (sm_ctxt.get_state (arg) == m_closed)
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<double_fclose> (*this,
diag_arg));
- sm_ctxt.set_next_state (stmt, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
return true;
}
void
fileptr_state_machine::on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const
if (op == NE_EXPR)
{
log ("got 'ARG != 0' match");
- sm_ctxt.on_transition (node, stmt,
- lhs, m_unchecked, m_nonnull);
+ sm_ctxt.on_transition (lhs, m_unchecked, m_nonnull);
}
else if (op == EQ_EXPR)
{
log ("got 'ARG == 0' match");
- sm_ctxt.on_transition (node, stmt,
- lhs, m_unchecked, m_null);
+ sm_ctxt.on_transition (lhs, m_unchecked, m_null);
}
}
kfm.add (BUILT_IN_VPRINTF, std::make_unique<kf_stdio_output_fn> ());
kfm.add ("ferror", std::make_unique<kf_ferror> ());
+ kfm.add ("ferror_unlocked", std::make_unique<kf_ferror> ());
kfm.add ("fgets", std::make_unique<kf_fgets> ());
kfm.add ("fgets_unlocked", std::make_unique<kf_fgets> ()); // non-standard
kfm.add ("fileno", std::make_unique<kf_fileno> ());
+ kfm.add ("fileno_unlocked", std::make_unique<kf_fileno> ());
kfm.add ("fread", std::make_unique<kf_fread> ());
+ kfm.add ("fread_unlocked", std::make_unique<kf_fread> ());
kfm.add ("getc", std::make_unique<kf_getc> ());
+ kfm.add ("getc_unlocked", std::make_unique<kf_getc> ());
kfm.add ("getchar", std::make_unique<kf_getchar> ());
+ kfm.add ("getchar_unlocked", std::make_unique<kf_getchar> ());
/* Some C++ implementations use the std:: copies of these functions
from <cstdio> for <stdio.h>, so we must match against these too. */
}
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
void on_phi (sm_context &sm_ctxt,
- const supernode *node,
const gphi *phi,
tree rhs) const final override;
+ void
+ check_call_preconditions (sm_context &sm_ctxt,
+ const call_details &cd) const final override;
+
void on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const final override;
static bool unaffected_by_call_p (tree fndecl);
void maybe_assume_non_null (sm_context &sm_ctxt,
- tree ptr,
- const gimple *stmt) const;
+ tree ptr) const;
void on_realloc_with_move (region_model *model,
sm_state_map *smap,
void
maybe_complain_about_deref_before_check (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const assumed_non_null_state *,
tree ptr) const;
const deallocator_set *deallocators,
bool returns_nonnull = false) const;
void handle_free_of_non_heap (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
tree arg,
const deallocator *d) const;
void on_deallocator_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
const deallocator *d,
unsigned argno) const;
void on_realloc_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call) const;
void on_zero_assignment (sm_context &sm_ctxt,
- const gimple *stmt,
tree lhs) const;
void handle_nonnull (sm_context &sm_ctx,
- const supernode *node,
- const gimple *stmt,
tree fndecl,
tree arg,
unsigned i) const;
const supernode *snode = point.get_supernode ();
if (!snode)
return false;
- for (auto &in_edge : snode->m_preds)
- {
- if (const cfg_superedge *cfg_in_edge
- = in_edge->dyn_cast_cfg_superedge ())
- if (cfg_in_edge->back_edge_p ())
- return true;
- }
+ for (auto in_edge : snode->m_bb->preds)
+ if (in_edge->flags & EDGE_DFS_BACK)
+ return true;
return false;
}
void
malloc_state_machine::maybe_assume_non_null (sm_context &sm_ctxt,
- tree ptr,
- const gimple *stmt) const
+ tree ptr) const
{
const region_model *old_model = sm_ctxt.get_old_region_model ();
if (!old_model)
state_t next_state
= mut_this->get_or_create_assumed_non_null_state_for_frame
(old_model->get_current_frame ());
- sm_ctxt.set_next_state (stmt, ptr, next_state);
+ sm_ctxt.set_next_state (ptr, next_state);
}
}
void
malloc_state_machine::handle_nonnull (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
tree fndecl,
tree arg,
unsigned i) const
{
- state_t state = sm_ctxt.get_state (stmt, arg);
+ state_t state = sm_ctxt.get_state (arg);
/* Can't use a switch as the states are non-const. */
/* Do use the fndecl that caused the warning so that the
misused attributes are printed and the user not confused. */
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
sm_ctxt.warn
- (node, stmt, arg,
+ (arg,
std::make_unique<possible_null_arg> (*this, diag_arg, fndecl,
i));
const allocation_state *astate
= as_a_allocation_state (state);
- sm_ctxt.set_next_state (stmt, arg, astate->get_nonnull ());
+ sm_ctxt.set_next_state (arg, astate->get_nonnull ());
}
else if (state == m_null)
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<null_arg> (*this, diag_arg, fndecl, i));
- sm_ctxt.set_next_state (stmt, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
else if (state == m_start)
- maybe_assume_non_null (sm_ctxt, arg, stmt);
+ maybe_assume_non_null (sm_ctxt, arg);
}
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
bool
malloc_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
|| is_named_call_p (callee_fndecl, "operator delete", call, 2))
{
- on_deallocator_call (sm_ctxt, node, call,
+ on_deallocator_call (sm_ctxt, call,
&m_scalar_delete.m_deallocator, 0);
return true;
}
else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1))
{
- on_deallocator_call (sm_ctxt, node, call,
+ on_deallocator_call (sm_ctxt, call,
&m_vector_delete.m_deallocator, 0);
return true;
}
{
tree lhs = gimple_call_lhs (&call);
if (lhs)
- sm_ctxt.on_transition (node, stmt, lhs, m_start, m_non_heap);
+ sm_ctxt.on_transition (lhs, m_start, m_non_heap);
return true;
}
|| is_std_named_call_p (callee_fndecl, "free", call, 1)
|| is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
{
- on_deallocator_call (sm_ctxt, node, call,
+ on_deallocator_call (sm_ctxt, call,
&m_free.m_deallocator, 0);
return true;
}
|| is_std_named_call_p (callee_fndecl, "realloc", call, 2)
|| is_named_call_p (callee_fndecl, "__builtin_realloc", call, 2))
{
- on_realloc_call (sm_ctxt, node, call);
+ on_realloc_call (sm_ctxt, call);
return true;
}
on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull);
}
- {
- /* Handle "__attribute__((nonnull))". */
- tree fntype = TREE_TYPE (fndecl);
- bitmap nonnull_args = get_nonnull_args (fntype);
- if (nonnull_args)
- {
- for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
- {
- tree arg = gimple_call_arg (stmt, i);
- if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
- continue;
- /* If we have a nonnull-args, and either all pointers, or
- just the specified pointers. */
- if (bitmap_empty_p (nonnull_args)
- || bitmap_bit_p (nonnull_args, i))
- handle_nonnull (sm_ctxt, node, stmt, fndecl, arg, i);
- }
- BITMAP_FREE (nonnull_args);
- }
- /* Handle __attribute__((nonnull_if_nonzero (x, y))). */
- if (fntype)
- for (tree attrs = TYPE_ATTRIBUTES (fntype);
- (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
- attrs = TREE_CHAIN (attrs))
- {
- tree args = TREE_VALUE (attrs);
- unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
- unsigned int idx2
- = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
- unsigned int idx3 = idx2;
- if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
- idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
- if (idx < gimple_call_num_args (stmt)
- && idx2 < gimple_call_num_args (stmt)
- && idx3 < gimple_call_num_args (stmt))
- {
- tree arg = gimple_call_arg (stmt, idx);
- tree arg2 = gimple_call_arg (stmt, idx2);
- tree arg3 = gimple_call_arg (stmt, idx3);
- if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
- || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
- || !INTEGRAL_TYPE_P (TREE_TYPE (arg3))
- || integer_zerop (arg2)
- || integer_zerop (arg3))
- continue;
- if (integer_nonzerop (arg2) && integer_nonzerop (arg3))
- ;
- else
- /* FIXME: Use ranger here to query arg2 and arg3
- ranges? */
- continue;
- handle_nonnull (sm_ctxt, node, stmt, fndecl, arg, idx);
- }
- }
- }
-
/* Check for this after nonnull, so that if we have both
then we transition to "freed", rather than "checked". */
unsigned dealloc_argno = fndecl_dealloc_argno (fndecl);
{
const deallocator *d
= mutable_this->get_or_create_deallocator (fndecl);
- on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno);
+ on_deallocator_call (sm_ctxt, call, d, dealloc_argno);
}
}
}
&& any_pointer_p (rhs)
&& zerop (rhs))
{
- state_t state = sm_ctxt.get_state (stmt, lhs);
+ state_t state = sm_ctxt.get_state (lhs);
if (assumed_non_null_p (state))
maybe_complain_about_deref_before_check
- (sm_ctxt, node,
- stmt,
+ (sm_ctxt,
(const assumed_non_null_state *)state,
lhs);
}
if (tree lhs = sm_ctxt.is_zero_assignment (stmt))
if (any_pointer_p (lhs))
- on_zero_assignment (sm_ctxt, stmt,lhs);
+ on_zero_assignment (sm_ctxt, lhs);
/* Handle dereferences. */
for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
{
tree arg = TREE_OPERAND (op, 0);
- state_t state = sm_ctxt.get_state (stmt, arg);
+ state_t state = sm_ctxt.get_state (arg);
if (state == m_start)
- maybe_assume_non_null (sm_ctxt, arg, stmt);
+ maybe_assume_non_null (sm_ctxt, arg);
else if (unchecked_p (state))
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<possible_null_deref> (*this,
diag_arg));
const allocation_state *astate = as_a_allocation_state (state);
- sm_ctxt.set_next_state (stmt, arg, astate->get_nonnull ());
+ sm_ctxt.set_next_state (arg, astate->get_nonnull ());
}
else if (state == m_null)
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<null_deref> (*this, diag_arg));
- sm_ctxt.set_next_state (stmt, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
else if (freed_p (state))
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
const allocation_state *astate = as_a_allocation_state (state);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<use_after_free>
(*this, diag_arg, astate->m_deallocator));
- sm_ctxt.set_next_state (stmt, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
}
}
void
malloc_state_machine::
maybe_complain_about_deref_before_check (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const assumed_non_null_state *state,
tree ptr) const
{
if (checked_in_frame->get_index () > assumed_nonnull_in_frame->get_index ())
return;
- /* Don't complain if STMT was inlined from another function, to avoid
+ /* Don't complain if code was inlined from another function, to avoid
similar false positives involving shared helper functions. */
- if (stmt->location)
+ if (location_t loc = sm_ctxt.get_emission_location ())
{
- inlining_info info (stmt->location);
+ inlining_info info (loc);
if (info.get_extra_frames () > 0)
return;
}
tree diag_ptr = sm_ctxt.get_diagnostic_tree (ptr);
if (diag_ptr)
sm_ctxt.warn
- (node, stmt, ptr,
+ (ptr,
std::make_unique<deref_before_check> (*this, diag_ptr));
- sm_ctxt.set_next_state (stmt, ptr, m_stop);
+ sm_ctxt.set_next_state (ptr, m_stop);
}
/* Handle a call to an allocator.
tree lhs = gimple_call_lhs (&call);
if (lhs)
{
- if (sm_ctxt.get_state (&call, lhs) == m_start)
- sm_ctxt.set_next_state (&call, lhs,
+ if (sm_ctxt.get_state (lhs) == m_start)
+ sm_ctxt.set_next_state (lhs,
(returns_nonnull
? deallocators->m_nonnull
: deallocators->m_unchecked));
void
malloc_state_machine::handle_free_of_non_heap (sm_context &sm_ctxt,
- const supernode *node,
- const gcall &call,
+ const gcall &,
tree arg,
const deallocator *d) const
{
const svalue *ptr_sval = old_model->get_rvalue (arg, nullptr);
freed_reg = old_model->deref_rvalue (ptr_sval, arg, nullptr);
}
- sm_ctxt.warn (node, &call, arg,
+ sm_ctxt.warn (arg,
std::make_unique<free_of_non_heap>
(*this, diag_arg, freed_reg, d->m_name));
- sm_ctxt.set_next_state (&call, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
void
malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
const deallocator *d,
unsigned argno) const
return;
tree arg = gimple_call_arg (&call, argno);
- state_t state = sm_ctxt.get_state (&call, arg);
+ state_t state = sm_ctxt.get_state (arg);
/* start/assumed_non_null/unchecked/nonnull -> freed. */
if (state == m_start || assumed_non_null_p (state))
- sm_ctxt.set_next_state (&call, arg, d->m_freed);
+ sm_ctxt.set_next_state (arg, d->m_freed);
else if (unchecked_p (state) || nonnull_p (state))
{
const allocation_state *astate = as_a_allocation_state (state);
{
/* Wrong allocator. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, &call, arg,
+ sm_ctxt.warn (arg,
std::make_unique<mismatching_deallocation>
(*this, diag_arg,
astate->m_deallocators,
d));
}
- sm_ctxt.set_next_state (&call, arg, d->m_freed);
+ sm_ctxt.set_next_state (arg, d->m_freed);
}
/* Keep state "null" as-is, rather than transitioning to "freed";
{
/* freed -> stop, with warning. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, &call, arg,
+ sm_ctxt.warn (arg,
std::make_unique<double_free> (*this, diag_arg, d->m_name));
- sm_ctxt.set_next_state (&call, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
}
else if (state == m_non_heap)
{
/* non-heap -> stop, with warning. */
- handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+ handle_free_of_non_heap (sm_ctxt, call, arg, d);
}
}
void
malloc_state_machine::on_realloc_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call) const
{
const unsigned argno = 0;
tree arg = gimple_call_arg (&call, argno);
- state_t state = sm_ctxt.get_state (&call, arg);
+ state_t state = sm_ctxt.get_state (arg);
if (unchecked_p (state) || nonnull_p (state))
{
{
/* Wrong allocator. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, &call, arg,
+ sm_ctxt.warn (arg,
std::make_unique<mismatching_deallocation>
(*this, diag_arg,
astate->m_deallocators, d));
- sm_ctxt.set_next_state (&call, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
if (path_context *path_ctxt = sm_ctxt.get_path_context ())
path_ctxt->terminate_path ();
}
{
/* freed -> stop, with warning. */
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, &call, arg,
+ sm_ctxt.warn (arg,
std::make_unique<double_free> (*this, diag_arg, "free"));
- sm_ctxt.set_next_state (&call, arg, m_stop);
+ sm_ctxt.set_next_state (arg, m_stop);
if (path_context *path_ctxt = sm_ctxt.get_path_context ())
path_ctxt->terminate_path ();
}
else if (state == m_non_heap)
{
/* non-heap -> stop, with warning. */
- handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+ handle_free_of_non_heap (sm_ctxt, call, arg, d);
if (path_context *path_ctxt = sm_ctxt.get_path_context ())
path_ctxt->terminate_path ();
}
void
malloc_state_machine::on_phi (sm_context &sm_ctxt,
- const supernode *node ATTRIBUTE_UNUSED,
const gphi *phi,
tree rhs) const
{
if (zerop (rhs))
{
tree lhs = gimple_phi_result (phi);
- on_zero_assignment (sm_ctxt, phi, lhs);
+ on_zero_assignment (sm_ctxt, lhs);
+ }
+}
+
+void
+malloc_state_machine::check_call_preconditions (sm_context &sm_ctxt,
+ const call_details &cd) const
+{
+ tree fndecl = cd.get_fndecl_for_call ();
+ if (!fndecl)
+ return;
+
+ const tree fntype = TREE_TYPE (fndecl);
+ const unsigned num_args = cd.num_args ();
+
+ /* Handle "__attribute__((nonnull))". */
+ if (bitmap nonnull_args = get_nonnull_args (fntype))
+ {
+ for (unsigned i = 0; i < num_args; i++)
+ {
+ tree arg = cd.get_arg_tree (i);
+ if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+ continue;
+ /* If we have a nonnull-args, and either all pointers, or
+ just the specified pointers. */
+ if (bitmap_empty_p (nonnull_args)
+ || bitmap_bit_p (nonnull_args, i))
+ handle_nonnull (sm_ctxt, fndecl, arg, i);
+ }
+ BITMAP_FREE (nonnull_args);
}
+
+ /* Handle __attribute__((nonnull_if_nonzero (x, y))). */
+ if (fntype)
+ for (tree attrs = TYPE_ATTRIBUTES (fntype);
+ (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+ attrs = TREE_CHAIN (attrs))
+ {
+ tree args = TREE_VALUE (attrs);
+ unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+ unsigned int idx2
+ = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+ unsigned int idx3 = idx2;
+ if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+ idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
+ if (idx < num_args
+ && idx2 < num_args
+ && idx3 < num_args)
+ {
+ tree arg = cd.get_arg_tree (idx);
+ tree arg2 = cd.get_arg_tree (idx2);
+ tree arg3 = cd.get_arg_tree (idx3);
+ if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
+ || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
+ || !INTEGRAL_TYPE_P (TREE_TYPE (arg3))
+ || integer_zerop (arg2)
+ || integer_zerop (arg3))
+ continue;
+ if (integer_nonzerop (arg2) && integer_nonzerop (arg3))
+ ;
+ else
+ /* FIXME: Use ranger here to query arg2 and arg3
+ ranges? */
+ continue;
+ handle_nonnull (sm_ctxt, fndecl, arg, idx);
+ }
+ }
}
/* Implementation of state_machine::on_condition vfunc for malloc_state_machine.
void
malloc_state_machine::on_condition (sm_context &sm_ctxt,
- const supernode *node ATTRIBUTE_UNUSED,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const
if (op == NE_EXPR)
{
log ("got 'ARG != 0' match");
- state_t s = sm_ctxt.get_state (stmt, lhs);
+ state_t s = sm_ctxt.get_state (lhs);
if (unchecked_p (s))
{
const allocation_state *astate = as_a_allocation_state (s);
- sm_ctxt.set_next_state (stmt, lhs, astate->get_nonnull ());
+ sm_ctxt.set_next_state (lhs, astate->get_nonnull ());
}
}
else if (op == EQ_EXPR)
{
log ("got 'ARG == 0' match");
- state_t s = sm_ctxt.get_state (stmt, lhs);
+ state_t s = sm_ctxt.get_state (lhs);
if (unchecked_p (s))
- sm_ctxt.set_next_state (stmt, lhs, m_null);
+ sm_ctxt.set_next_state (lhs, m_null);
}
}
void
malloc_state_machine::on_zero_assignment (sm_context &sm_ctxt,
- const gimple *stmt,
tree lhs) const
{
- state_t s = sm_ctxt.get_state (stmt, lhs);
+ state_t s = sm_ctxt.get_state (lhs);
enum resource_state rs = get_rs (s);
if (rs == RS_START
|| rs == RS_UNCHECKED
|| rs == RS_NONNULL
|| rs == RS_FREED)
- sm_ctxt.set_next_state (stmt, lhs, m_null);
+ sm_ctxt.set_next_state (lhs, m_null);
}
/* Special-case hook for handling realloc, for the "success with move to
bool inherited_state_p () const final override { return false; }
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
void on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const final override;
bool
pattern_test_state_machine::on_stmt (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
- const supernode *node ATTRIBUTE_UNUSED,
const gimple *stmt ATTRIBUTE_UNUSED) const
{
return false;
void
pattern_test_state_machine::on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const
{
- if (stmt == nullptr)
- return;
-
tree rhs_cst = rhs->maybe_get_constant ();
if (!rhs_cst)
return;
if (tree lhs_expr = sm_ctxt.get_diagnostic_tree (lhs))
{
- sm_ctxt.warn (node, stmt, lhs_expr,
+ sm_ctxt.warn (lhs_expr,
std::make_unique<pattern_match> (lhs_expr, op, rhs_cst));
}
}
bool inherited_state_p () const final override { return true; }
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
bool can_purge_p (state_t s) const final override;
private:
void warn_for_any_exposure (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
tree arg) const;
};
void
sensitive_state_machine::warn_for_any_exposure (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
tree arg) const
{
- if (sm_ctxt.get_state (stmt, arg) == m_sensitive)
+ if (sm_ctxt.get_state (arg) == m_sensitive)
{
tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
- sm_ctxt.warn (node, stmt, arg,
+ sm_ctxt.warn (arg,
std::make_unique<exposure_through_output_file> (*this,
diag_arg));
}
bool
sensitive_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
if (const gcall *call = dyn_cast <const gcall *> (stmt))
{
tree lhs = gimple_call_lhs (call);
if (lhs)
- sm_ctxt.on_transition (node, stmt, lhs, m_start, m_sensitive);
+ sm_ctxt.on_transition (lhs, m_start, m_sensitive);
return true;
}
else if (is_named_call_p (callee_fndecl, "fprintf")
for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
{
tree arg = gimple_call_arg (call, idx);
- warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+ warn_for_any_exposure (sm_ctxt, arg);
}
return true;
}
else if (is_named_call_p (callee_fndecl, "fwrite", *call, 4))
{
tree arg = gimple_call_arg (call, 0);
- warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+ warn_for_any_exposure (sm_ctxt, arg);
return true;
}
// TODO: ...etc. This is just a proof-of-concept at this point.
bool inherited_state_p () const final override { return false; }
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
bool can_purge_p (state_t s) const final override;
}
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge ATTRIBUTE_UNUSED)
+ const exploded_edge &eedge ATTRIBUTE_UNUSED,
+ pending_diagnostic &)
const final override
{
emission_path->add_event
= program_point::from_function_entry (*ext_state.get_model_manager (),
eg->get_supergraph (),
*handler_fun);
-
program_state state_entering_handler (ext_state);
update_model_for_signal_handler (state_entering_handler.m_region_model,
*handler_fun);
bool
signal_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
const state_t global_state = sm_ctxt.get_global_state ();
if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (*call))
if (signal_unsafe_p (callee_fndecl))
if (sm_ctxt.get_global_state () == m_in_signal_handler)
- sm_ctxt.warn (node, stmt, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<signal_unsafe_call>
(*this, *call, callee_fndecl));
}
}
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
void on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const final override;
void on_bounded_ranges (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue &sval,
const bounded_ranges &ranges) const final override;
private:
void check_control_flow_arg_for_taint (sm_context &sm_ctxt,
- const gimple *stmt,
tree expr) const;
void check_for_tainted_size_arg (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
tree callee_fndecl) const;
void check_for_tainted_divisor (sm_context &sm_ctxt,
- const supernode *node,
const gassign *assign) const;
public:
bool
taint_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
if (const gcall *call = dyn_cast <const gcall *> (stmt))
{
tree arg = gimple_call_arg (call, 0);
- sm_ctxt.on_transition (node, stmt, arg, m_start, m_tainted);
+ sm_ctxt.on_transition (arg, m_start, m_tainted);
/* Dereference an ADDR_EXPR. */
// TODO: should the engine do this?
if (TREE_CODE (arg) == ADDR_EXPR)
- sm_ctxt.on_transition (node, stmt, TREE_OPERAND (arg, 0),
+ sm_ctxt.on_transition (TREE_OPERAND (arg, 0),
m_start, m_tainted);
return true;
}
/* External function with "access" attribute. */
if (sm_ctxt.unknown_side_effects_p ())
- check_for_tainted_size_arg (sm_ctxt, node, *call, callee_fndecl);
+ check_for_tainted_size_arg (sm_ctxt, *call, callee_fndecl);
if (is_assertion_failure_handler_p (callee_fndecl)
&& sm_ctxt.get_global_state () == m_tainted_control_flow)
{
- sm_ctxt.warn (node, call, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<tainted_assertion> (*this, NULL_TREE,
callee_fndecl));
}
case ROUND_MOD_EXPR:
case RDIV_EXPR:
case EXACT_DIV_EXPR:
- check_for_tainted_divisor (sm_ctxt, node, assign);
+ check_for_tainted_divisor (sm_ctxt, assign);
break;
}
}
control flow statement, so that only the last one before
an assertion-failure-handler counts. */
sm_ctxt.set_global_state (m_start);
- check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_lhs (cond));
- check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_rhs (cond));
+ check_control_flow_arg_for_taint (sm_ctxt, gimple_cond_lhs (cond));
+ check_control_flow_arg_for_taint (sm_ctxt, gimple_cond_rhs (cond));
}
if (const gswitch *switch_ = dyn_cast <const gswitch *> (stmt))
control flow statement, so that only the last one before
an assertion-failure-handler counts. */
sm_ctxt.set_global_state (m_start);
- check_control_flow_arg_for_taint (sm_ctxt, switch_,
+ check_control_flow_arg_for_taint (sm_ctxt,
gimple_switch_index (switch_));
}
void
taint_state_machine::check_control_flow_arg_for_taint (sm_context &sm_ctxt,
- const gimple *stmt,
tree expr) const
{
const region_model *old_model = sm_ctxt.get_old_region_model ();
const svalue *sval = old_model->get_rvalue (expr, nullptr);
- state_t state = sm_ctxt.get_state (stmt, sval);
+ state_t state = sm_ctxt.get_state (sval);
enum bounds b;
if (get_taint (state, TREE_TYPE (expr), &b))
sm_ctxt.set_global_state (m_tainted_control_flow);
void
taint_state_machine::on_condition (sm_context &sm_ctxt,
- const supernode *node,
- const gimple *stmt,
const svalue *lhs,
enum tree_code op,
const svalue *rhs) const
{
- if (stmt == nullptr)
- return;
-
if (lhs->get_kind () == SK_UNKNOWN
|| rhs->get_kind () == SK_UNKNOWN)
{
/* (LHS >= RHS) or (LHS > RHS)
LHS gains a lower bound
RHS gains an upper bound. */
- sm_ctxt.on_transition (node, stmt, lhs, m_tainted, m_has_lb);
- sm_ctxt.on_transition (node, stmt, lhs, m_has_ub, m_stop);
- sm_ctxt.on_transition (node, stmt, rhs, m_tainted, m_has_ub);
- sm_ctxt.on_transition (node, stmt, rhs, m_has_lb, m_stop);
+ sm_ctxt.on_transition (lhs, m_tainted, m_has_lb);
+ sm_ctxt.on_transition (lhs, m_has_ub, m_stop);
+ sm_ctxt.on_transition (rhs, m_tainted, m_has_ub);
+ sm_ctxt.on_transition (rhs, m_has_lb, m_stop);
}
break;
case LE_EXPR:
both conditions simultaneously (we'd have a transition
from the old state to has_lb, then a transition from
the old state *again* to has_ub). */
- state_t old_state
- = sm_ctxt.get_state (stmt, inner_lhs);
+ state_t old_state = sm_ctxt.get_state (inner_lhs);
if (old_state == m_tainted
|| old_state == m_has_lb
|| old_state == m_has_ub)
- sm_ctxt.set_next_state (stmt, inner_lhs, m_stop);
+ sm_ctxt.set_next_state (inner_lhs, m_stop);
return;
}
}
/* (LHS <= RHS) or (LHS < RHS)
LHS gains an upper bound
RHS gains a lower bound. */
- sm_ctxt.on_transition (node, stmt, lhs, m_tainted, m_has_ub);
- sm_ctxt.on_transition (node, stmt, lhs, m_has_lb, m_stop);
- sm_ctxt.on_transition (node, stmt, rhs, m_tainted, m_has_lb);
- sm_ctxt.on_transition (node, stmt, rhs, m_has_ub, m_stop);
+ sm_ctxt.on_transition (lhs, m_tainted, m_has_ub);
+ sm_ctxt.on_transition (lhs, m_has_lb, m_stop);
+ sm_ctxt.on_transition (rhs, m_tainted, m_has_lb);
+ sm_ctxt.on_transition (rhs, m_has_ub, m_stop);
}
break;
default:
void
taint_state_machine::on_bounded_ranges (sm_context &sm_ctxt,
- const supernode *,
- const gimple *stmt,
const svalue &sval,
const bounded_ranges &ranges) const
{
/* We have new bounds from the ranges; combine them with any
existing bounds on SVAL. */
- state_t old_state = sm_ctxt.get_state (stmt, &sval);
+ state_t old_state = sm_ctxt.get_state (&sval);
if (old_state == m_tainted)
{
if (ranges_have_lb && ranges_have_ub)
- sm_ctxt.set_next_state (stmt, &sval, m_stop);
+ sm_ctxt.set_next_state (&sval, m_stop);
else if (ranges_have_lb)
- sm_ctxt.set_next_state (stmt, &sval, m_has_lb);
+ sm_ctxt.set_next_state (&sval, m_has_lb);
else if (ranges_have_ub)
- sm_ctxt.set_next_state (stmt, &sval, m_has_ub);
+ sm_ctxt.set_next_state (&sval, m_has_ub);
}
else if (old_state == m_has_ub && ranges_have_lb)
- sm_ctxt.set_next_state (stmt, &sval, m_stop);
+ sm_ctxt.set_next_state (&sval, m_stop);
else if (old_state == m_has_lb && ranges_have_ub)
- sm_ctxt.set_next_state (stmt, &sval, m_stop);
+ sm_ctxt.set_next_state (&sval, m_stop);
}
bool
void
taint_state_machine::check_for_tainted_size_arg (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
tree callee_fndecl) const
{
tree size_arg = gimple_call_arg (&call, access->sizarg);
- state_t state = sm_ctxt.get_state (&call, size_arg);
+ state_t state = sm_ctxt.get_state (size_arg);
enum bounds b;
if (get_taint (state, TREE_TYPE (size_arg), &b))
{
const char* const access_str =
TREE_STRING_POINTER (access->to_external_string ());
tree diag_size = sm_ctxt.get_diagnostic_tree (size_arg);
- sm_ctxt.warn (node, &call, size_arg,
+ sm_ctxt.warn (size_arg,
std::make_unique<tainted_access_attrib_size>
(*this, diag_size, b,
callee_fndecl,
void
taint_state_machine::check_for_tainted_divisor (sm_context &sm_ctxt,
- const supernode *node,
const gassign *assign) const
{
const region_model *old_model = sm_ctxt.get_old_region_model ();
const svalue *divisor_sval = old_model->get_rvalue (divisor_expr, nullptr);
- state_t state = sm_ctxt.get_state (assign, divisor_sval);
+ state_t state = sm_ctxt.get_state (divisor_sval);
enum bounds b;
if (get_taint (state, TREE_TYPE (divisor_expr), &b))
{
tree diag_divisor = sm_ctxt.get_diagnostic_tree (divisor_expr);
sm_ctxt.warn
- (node, assign, divisor_expr,
+ (divisor_expr,
std::make_unique <tainted_divisor> (*this, diag_divisor, b));
- sm_ctxt.set_next_state (assign, divisor_sval, m_stop);
+ sm_ctxt.set_next_state (divisor_sval, m_stop);
}
}
}
void
-state_machine::add_state_to_state_graph (analyzer_state_graph &out_state_graph,
- const svalue &sval,
- state_machine::state_t state) const
+state_machine::add_state_to_state_graph (analyzer_state_graph &/*out_state_graph*/,
+ const svalue &/*sval*/,
+ state_machine::state_t /*state*/) const
{
// no-op
}
void
-state_machine::add_global_state_to_state_graph (analyzer_state_graph &out_state_graph,
- state_machine::state_t state) const
+state_machine::add_global_state_to_state_graph (analyzer_state_graph &/*out_state_graph*/,
+ state_machine::state_t /*state*/) const
{
// no-op
}
#ifndef GCC_ANALYZER_SM_H
#define GCC_ANALYZER_SM_H
+#include "analyzer/analyzer-logging.h"
+
/* Utility functions for use by state machines. */
namespace ana {
/* Return true if STMT is a function call recognized by this sm. */
virtual bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const = 0;
virtual void on_phi (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
- const supernode *node ATTRIBUTE_UNUSED,
const gphi *phi ATTRIBUTE_UNUSED,
tree rhs ATTRIBUTE_UNUSED) const
{
}
+ virtual void
+ check_call_preconditions (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
+ const call_details &cd ATTRIBUTE_UNUSED) const
+ {
+ }
+
virtual void on_condition (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
- const supernode *node ATTRIBUTE_UNUSED,
- const gimple *stmt ATTRIBUTE_UNUSED,
const svalue *lhs ATTRIBUTE_UNUSED,
enum tree_code op ATTRIBUTE_UNUSED,
const svalue *rhs ATTRIBUTE_UNUSED) const
virtual void
on_bounded_ranges (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
- const supernode *node ATTRIBUTE_UNUSED,
- const gimple *stmt ATTRIBUTE_UNUSED,
const svalue &sval ATTRIBUTE_UNUSED,
const bounded_ranges &ranges ATTRIBUTE_UNUSED) const
{
};
/* Abstract base class giving an interface for the state machine to call
- the checker engine, at a particular stmt. */
+ the checker engine, at a particular code location. */
class sm_context
{
other callback handling. */
virtual tree get_fndecl_for_call (const gcall &call) = 0;
- /* Get the old state of VAR at STMT. */
- virtual state_machine::state_t get_state (const gimple *stmt,
- tree var) = 0;
- virtual state_machine::state_t get_state (const gimple *stmt,
- const svalue *) = 0;
+ /* Get the old state of VAR. */
+ virtual state_machine::state_t get_state (tree var) = 0;
+ virtual state_machine::state_t get_state (const svalue *) = 0;
+
/* Set the next state of VAR to be TO, recording the "origin" of the
- state as ORIGIN.
- Use STMT for location information. */
- virtual void set_next_state (const gimple *stmt,
- tree var,
+ state as ORIGIN. */
+ virtual void set_next_state (tree var,
state_machine::state_t to,
tree origin = NULL_TREE) = 0;
- virtual void set_next_state (const gimple *stmt,
- const svalue *var,
+ virtual void set_next_state (const svalue *var,
state_machine::state_t to,
tree origin = NULL_TREE) = 0;
/* Called by state_machine in response to pattern matches:
if VAR is in state FROM, transition it to state TO, potentially
- recording the "origin" of the state as ORIGIN.
- Use NODE and STMT for location information. */
- void on_transition (const supernode *node ATTRIBUTE_UNUSED,
- const gimple *stmt,
- tree var,
+ recording the "origin" of the state as ORIGIN. */
+ void on_transition (tree var,
state_machine::state_t from,
state_machine::state_t to,
tree origin = NULL_TREE)
{
- state_machine::state_t current = get_state (stmt, var);
+ state_machine::state_t current = get_state (var);
if (current == from)
- set_next_state (stmt, var, to, origin);
+ set_next_state (var, to, origin);
}
- void on_transition (const supernode *node ATTRIBUTE_UNUSED,
- const gimple *stmt,
- const svalue *var,
+ void on_transition (const svalue *var,
state_machine::state_t from,
state_machine::state_t to,
tree origin = NULL_TREE)
{
- state_machine::state_t current = get_state (stmt, var);
+ state_machine::state_t current = get_state (var);
if (current == from)
- set_next_state (stmt, var, to, origin);
+ set_next_state (var, to, origin);
}
/* Called by state_machine in response to pattern matches:
- issue a diagnostic D using NODE and STMT for location information. */
- virtual void warn (const supernode *node, const gimple *stmt,
- tree var,
+ issue a diagnostic D about VAR. */
+ virtual void warn (tree var,
std::unique_ptr<pending_diagnostic> d) = 0;
- virtual void warn (const supernode *node, const gimple *stmt,
- const svalue *var,
+ virtual void warn (const svalue *var,
std::unique_ptr<pending_diagnostic> d) = 0;
/* For use when generating trees when creating pending_diagnostics, so that
const region_model *get_old_region_model () const;
+ /* Get the location a diagnostic would be emitted at. */
+ virtual location_t get_emission_location () const = 0;
+
protected:
sm_context (int sm_idx, const state_machine &sm)
: m_sm_idx (sm_idx), m_sm (sm) {}
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
+#define INCLUDE_SET
#include "analyzer/common.h"
#include "timevar.h"
}
/* Class-based handler for walk_stmt_load_store_addr_ops at a particular
- function_point, for populating the worklists within a state_purge_map. */
+ ana::operation, for populating the worklists within a state_purge_map. */
class gimple_op_visitor : public log_user
{
public:
gimple_op_visitor (state_purge_map *map,
- const function_point &point,
- const function &fun)
+ const superedge &sedge)
: log_user (map->get_logger ()),
m_map (map),
- m_point (point),
- m_fun (fun)
- {}
+ m_sedge (sedge),
+ m_fun (sedge.m_src->get_function ())
+ {
+ gcc_assert (m_fun);
+ }
bool on_load (gimple *stmt, tree base, tree op)
{
gcc_assert (get_candidate_for_purging (decl) == decl);
state_purge_per_decl &data
= get_or_create_data_for_decl (decl);
- data.add_needed_at (m_point);
-
- /* Handle calls: if we're seeing a use at a call, then add a use at the
- "after-supernode" point (in case of interprocedural call superedges). */
- if (m_point.final_stmt_p ())
- data.add_needed_at (m_point.get_next ());
+ data.add_needed_at (*m_sedge.m_src);
}
void add_pointed_to (tree decl)
{
gcc_assert (get_candidate_for_purging (decl) == decl);
- get_or_create_data_for_decl (decl).add_pointed_to_at (m_point);
+ get_or_create_data_for_decl (decl).add_pointed_to_at (*m_sedge.m_src);
}
state_purge_per_decl &
get_or_create_data_for_decl (tree decl)
{
- return m_map->get_or_create_data_for_decl (m_fun, decl);
+ return m_map->get_or_create_data_for_decl (*m_fun, decl);
}
state_purge_map *m_map;
- const function_point &m_point;
- const function &m_fun;
+ const superedge &m_sedge;
+ const function *m_fun;
};
static bool
}
/* Find all uses of local vars.
- We iterate through all function points, finding loads, stores, and
+ We iterate through all operations, finding loads, stores, and
address-taken operations on locals, building a pair of worklists. */
- for (auto snode : sg.m_nodes)
+ for (auto sedge : sg.m_edges)
{
if (logger)
- log ("SN: %i", snode->m_index);
- /* We ignore m_returning_call and phi nodes. */
- gimple *stmt;
- unsigned i;
- FOR_EACH_VEC_ELT (snode->m_stmts, i, stmt)
+ log ("edge: SN %i -> SN %i",
+ sedge->m_src->m_id,
+ sedge->m_dest->m_id);
+ if (auto op = sedge->get_op ())
{
- function *fun = snode->get_function ();
- gcc_assert (fun);
- function_point point (function_point::before_stmt (snode, i));
- gimple_op_visitor v (this, point, *fun);
- walk_stmt_load_store_addr_ops (stmt, &v,
- my_load_cb, my_store_cb, my_addr_cb);
+ gimple_op_visitor v (this, *sedge);
+ op->walk_load_store_addr_ops (&v,
+ my_load_cb, my_store_cb, my_addr_cb);
}
}
return *result;
}
+void
+state_purge_map::on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode)
+{
+ for (auto iter : m_ssa_map)
+ iter.second->on_duplicated_node (old_snode, new_snode);
+ for (auto iter : m_decl_map)
+ iter.second->on_duplicated_node (old_snode, new_snode);
+}
+
/* class state_purge_per_ssa_name : public state_purge_per_tree. */
/* state_purge_per_ssa_name's ctor.
state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map,
tree name,
const function &fun)
-: state_purge_per_tree (fun), m_points_needing_name (), m_name (name)
+: state_purge_per_tree (fun), m_snodes_needing_name (), m_name (name)
{
LOG_FUNC (map.get_logger ());
map.log ("def stmt: %s", pp_formatted_text (&pp));
}
- auto_vec<function_point> worklist;
+ auto_vec<const supernode *> worklist;
/* Add all immediate uses of name to the worklist.
Compare with debug_immediate_uses. */
continue;
}
- const supernode *snode
- = map.get_sg ().get_supernode_for_stmt (use_stmt);
-
/* If it's a use within a phi node, then we care about
which in-edge we came from. */
if (use_stmt->code == GIMPLE_PHI)
{
- for (gphi_iterator gpi
- = const_cast<supernode *> (snode)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
+ const gphi *phi = as_a <const gphi *> (use_stmt);
+ /* Find arguments (and thus CFG in-edges) which use NAME. */
+ for (unsigned arg_idx = 0;
+ arg_idx < gimple_phi_num_args (phi);
+ ++arg_idx)
{
- gphi *phi = gpi.phi ();
- if (phi == use_stmt)
+ if (name == gimple_phi_arg (phi, arg_idx)->def)
{
- /* Find arguments (and thus in-edges) which use NAME. */
- for (unsigned arg_idx = 0;
- arg_idx < gimple_phi_num_args (phi);
- ++arg_idx)
+ edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
+ const superedge *in_sedge
+ = map.get_sg ().get_superedge_for_phis (in_edge);
+ if (in_sedge)
+ {
+ add_to_worklist (*in_sedge->m_src,
+ &worklist,
+ map.get_logger ());
+ m_snodes_needing_name.add (in_sedge->m_src);
+ }
+ else
{
- if (name == gimple_phi_arg (phi, arg_idx)->def)
- {
- edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
- const superedge *in_sedge
- = map.get_sg ().get_edge_for_cfg_edge (in_edge);
- function_point point
- = function_point::before_supernode
- (snode, in_sedge);
- add_to_worklist (point, &worklist,
- map.get_logger ());
- m_points_needing_name.add (point);
- }
+ /* Should only happen for abnormal edges, which
+ get skipped in supergraph construction. */
+ gcc_assert (in_edge->flags & EDGE_ABNORMAL);
}
}
}
}
else
{
- function_point point = before_use_stmt (map, use_stmt);
- add_to_worklist (point, &worklist, map.get_logger ());
- m_points_needing_name.add (point);
-
- /* We also need to add uses for conditionals and switches,
- where the stmt "happens" at the after_supernode, for filtering
- the out-edges. */
- if (use_stmt == snode->get_last_stmt ())
- {
- if (map.get_logger ())
- map.log ("last stmt in BB");
- function_point point
- = function_point::after_supernode (snode);
- add_to_worklist (point, &worklist, map.get_logger ());
- m_points_needing_name.add (point);
- }
- else
- if (map.get_logger ())
- map.log ("not last stmt in BB");
+ const supernode *snode
+ = map.get_sg ().get_supernode_for_stmt (use_stmt);
+ add_to_worklist (*snode, &worklist, map.get_logger ());
+ m_snodes_needing_name.add (snode);
}
}
}
log_scope s (map.get_logger (), "processing worklist");
while (worklist.length () > 0)
{
- function_point point = worklist.pop ();
- process_point (point, &worklist, map);
+ const supernode *snode = worklist.pop ();
+ gcc_assert (snode);
+ process_supernode (*snode, &worklist, map);
}
}
if (map.get_logger ())
{
map.log ("%qE in %qD is needed to process:", name, fun.decl);
- /* Log m_points_needing_name, sorting it to avoid churn when comparing
+ /* Log m_snodes_needing_name, sorting it to avoid churn when comparing
dumps. */
- auto_vec<function_point> points;
- for (point_set_t::iterator iter = m_points_needing_name.begin ();
- iter != m_points_needing_name.end ();
- ++iter)
- points.safe_push (*iter);
- points.qsort (function_point::cmp_ptr);
- unsigned i;
- function_point *point;
- FOR_EACH_VEC_ELT (points, i, point)
- {
- map.start_log_line ();
- map.get_logger ()->log_partial (" point: ");
- point->print (map.get_logger ()->get_printer (), format (false));
- map.end_log_line ();
- }
+ std::set<int> indices;
+ auto_vec<const supernode *> snodes;
+ for (auto iter : m_snodes_needing_name)
+ indices.insert (iter->m_id);
+ for (auto iter : indices)
+ map.get_logger ()->log (" SN %i", iter);
}
}
/* Return true if the SSA name is needed at POINT. */
bool
-state_purge_per_ssa_name::needed_at_point_p (const function_point &point) const
-{
- return const_cast <point_set_t &> (m_points_needing_name).contains (point);
-}
-
-/* Get the function_point representing immediately before USE_STMT.
- Subroutine of ctor. */
-
-function_point
-state_purge_per_ssa_name::before_use_stmt (const state_purge_map &map,
- const gimple *use_stmt)
+state_purge_per_ssa_name::needed_at_supernode_p (const supernode *snode) const
{
- gcc_assert (use_stmt->code != GIMPLE_PHI);
-
- const supernode *supernode
- = map.get_sg ().get_supernode_for_stmt (use_stmt);
- unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
- return function_point::before_stmt (supernode, stmt_idx);
+ return const_cast <point_set_t &> (m_snodes_needing_name).contains (snode);
}
-/* Add POINT to *WORKLIST if the point has not already been seen.
+/* Add SNODE to *WORKLIST if the supernode has not already been seen.
Subroutine of ctor. */
void
-state_purge_per_ssa_name::add_to_worklist (const function_point &point,
- auto_vec<function_point> *worklist,
+state_purge_per_ssa_name::add_to_worklist (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
logger *logger)
{
LOG_FUNC (logger);
- if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("point: '");
- point.print (logger->get_printer (), format (false));
- logger->log_partial ("' for worklist for %qE", m_name);
- logger->end_log_line ();
- }
- gcc_assert (point.get_function () == &get_function ());
- if (point.get_from_edge ())
- gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);
+ gcc_assert (snode.get_function () == &get_function ());
- if (m_points_needing_name.contains (point))
+ if (m_snodes_needing_name.contains (&snode))
{
if (logger)
- logger->log ("already seen for %qE", m_name);
+ logger->log ("SN %i already seen for %qE", snode.m_id, m_name);
}
else
{
if (logger)
- logger->log ("not seen; adding to worklist for %qE", m_name);
- m_points_needing_name.add (point);
- worklist->safe_push (point);
+ logger->log ("not seen; adding SN %i to worklist for %qE",
+ snode.m_id, m_name);
+ m_snodes_needing_name.add (&snode);
+ worklist->safe_push (&snode);
}
}
-/* Return true iff NAME is used by any of the phi nodes in SNODE
- when processing the in-edge with PHI_ARG_IDX. */
-
-static bool
-name_used_by_phis_p (tree name, const supernode *snode,
- size_t phi_arg_idx)
-{
- gcc_assert (TREE_CODE (name) == SSA_NAME);
-
- for (gphi_iterator gpi
- = const_cast<supernode *> (snode)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
- {
- gphi *phi = gpi.phi ();
- if (gimple_phi_arg_def (phi, phi_arg_idx) == name)
- return true;
- }
- return false;
-}
-
-/* Process POINT, popped from WORKLIST.
- Iterate over predecessors of POINT, adding to WORKLIST. */
+/* Process SNODE, popped from WORKLIST.
+ Iterate over predecessors of SNODE, adding to WORKLIST. */
void
-state_purge_per_ssa_name::process_point (const function_point &point,
- auto_vec<function_point> *worklist,
- const state_purge_map &map)
+state_purge_per_ssa_name::process_supernode (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
+ const state_purge_map &map)
{
logger *logger = map.get_logger ();
LOG_FUNC (logger);
if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("considering point: '");
- point.print (logger->get_printer (), format (false));
- logger->log_partial ("' for %qE", m_name);
- logger->end_log_line ();
- }
-
- gimple *def_stmt = SSA_NAME_DEF_STMT (m_name);
-
- const supernode *snode = point.get_supernode ();
+ logger->log ("considering SN %i for %qE", snode.m_id, m_name);
- switch (point.get_kind ())
+ for (auto in_edge : snode.m_preds)
{
- default:
- gcc_unreachable ();
-
- case PK_ORIGIN:
- break;
-
- case PK_BEFORE_SUPERNODE:
- {
- for (gphi_iterator gpi
- = const_cast<supernode *> (snode)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
- {
- gcc_assert (point.get_from_edge ());
- const cfg_superedge *cfg_sedge
- = point.get_from_edge ()->dyn_cast_cfg_superedge ();
- gcc_assert (cfg_sedge);
-
- gphi *phi = gpi.phi ();
- /* Are we at the def-stmt for m_name? */
- if (phi == def_stmt)
- {
- if (name_used_by_phis_p (m_name, snode,
- cfg_sedge->get_phi_arg_idx ()))
- {
- if (logger)
- logger->log ("name in def stmt used within phis;"
- " continuing");
- }
- else
- {
- if (logger)
- logger->log ("name in def stmt not used within phis;"
- " terminating");
- return;
- }
- }
- }
-
- /* Add given pred to worklist. */
- if (point.get_from_edge ())
- {
- gcc_assert (point.get_from_edge ()->m_src);
- add_to_worklist
- (function_point::after_supernode (point.get_from_edge ()->m_src),
- worklist, logger);
- }
- else
- {
- /* Add any intraprocedually edge for a call. */
- if (snode->m_returning_call)
- {
- gcall *returning_call = snode->m_returning_call;
- cgraph_edge *cedge
- = supergraph_call_edge (snode->m_fun,
- returning_call);
- if(cedge)
- {
- superedge *sedge
- = map.get_sg ().get_intraprocedural_edge_for_call (cedge);
- gcc_assert (sedge);
- add_to_worklist
- (function_point::after_supernode (sedge->m_src),
- worklist, logger);
- }
- else
- {
- supernode *callernode
- = map.get_sg ().get_supernode_for_stmt (returning_call);
-
- gcc_assert (callernode);
- add_to_worklist
- (function_point::after_supernode (callernode),
- worklist, logger);
- }
- }
- }
- }
- break;
-
- case PK_BEFORE_STMT:
- {
- if (def_stmt == point.get_stmt ())
- {
- if (logger)
- logger->log ("def stmt; terminating");
- return;
- }
- if (point.get_stmt_idx () > 0)
- add_to_worklist (function_point::before_stmt
- (snode, point.get_stmt_idx () - 1),
- worklist, logger);
- else
+ if (logger)
+ logger->log ("considering edge from SN %i", in_edge->m_src->m_id);
+ bool defines_ssa_name = false;
+ if (auto op = in_edge->get_op ())
+ if (op->defines_ssa_name_p (m_name))
+ defines_ssa_name = true;
+ if (defines_ssa_name)
{
- /* Add before_supernode to worklist. This captures the in-edge,
- so we have to do it once per in-edge. */
- unsigned i;
- superedge *pred;
- FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
- add_to_worklist (function_point::before_supernode (snode,
- pred),
- worklist, logger);
+ if (logger)
+ logger->log ("op defines %qE", m_name);
}
- }
- break;
-
- case PK_AFTER_SUPERNODE:
- {
- if (snode->m_stmts.length ())
- add_to_worklist
- (function_point::before_stmt (snode,
- snode->m_stmts.length () - 1),
- worklist, logger);
- else
- {
- /* Add before_supernode to worklist. This captures the in-edge,
- so we have to do it once per in-edge. */
- unsigned i;
- superedge *pred;
- FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
- add_to_worklist (function_point::before_supernode (snode,
- pred),
- worklist, logger);
- /* If it's the initial BB, add it, to ensure that we
- have "before supernode" for the initial ENTRY block, and don't
- erroneously purge SSA names for initial values of parameters. */
- if (snode->entry_p ())
- {
- add_to_worklist
- (function_point::before_supernode (snode, nullptr),
- worklist, logger);
- }
- }
- }
- break;
+ else
+ add_to_worklist (*in_edge->m_src, worklist, logger);
}
}
+void
+state_purge_per_ssa_name::on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode)
+{
+ if (m_snodes_needing_name.contains (&old_snode))
+ m_snodes_needing_name.add (&new_snode);
+}
+
/* class state_purge_per_decl : public state_purge_per_tree. */
/* state_purge_per_decl's ctor. */
if (TREE_CODE (decl) == RESULT_DECL)
{
supernode *exit_snode = map.get_sg ().get_node_for_function_exit (fun);
- add_needed_at (function_point::after_supernode (exit_snode));
+ add_needed_at (*exit_snode);
}
}
/* Mark the value of the decl (or a subvalue within it) as being needed
- at POINT. */
+ at SNODE. */
void
-state_purge_per_decl::add_needed_at (const function_point &point)
+state_purge_per_decl::add_needed_at (const supernode &snode)
{
- m_points_needing_decl.add (point);
+ m_snodes_needing_decl.add (&snode);
}
/* Mark that a pointer to the decl (or a region within it) is taken
- at POINT. */
+ at SNODE. */
void
-state_purge_per_decl::add_pointed_to_at (const function_point &point)
+state_purge_per_decl::add_pointed_to_at (const supernode &snode)
{
- m_points_taking_address.add (point);
+ m_snodes_taking_address.add (&snode);
}
/* Process the worklists for this decl:
- (a) walk backwards from points where we know the value of the decl
- is needed, marking points until we get to a stmt that fully overwrites
+ (a) walk backwards from snodes where we know the value of the decl
+ is needed, marking snodes until we get to a stmt that fully overwrites
the decl.
- (b) walk forwards from points where the address of the decl is taken,
- marking points as potentially needing the value of the decl. */
+ (b) walk forwards from snodes where the address of the decl is taken,
+ marking snodes as potentially needing the value of the decl. */
void
state_purge_per_decl::process_worklists (const state_purge_map &map,
/* Worklist for walking backwards from uses. */
{
- auto_vec<function_point> worklist;
+ auto_vec<const supernode *> worklist;
point_set_t seen;
/* Add all uses of the decl to the worklist. */
- for (auto iter : m_points_needing_decl)
+ for (auto iter : m_snodes_needing_decl)
worklist.safe_push (iter);
region_model model (mgr);
log_scope s (logger, "processing worklist");
while (worklist.length () > 0)
{
- function_point point = worklist.pop ();
- process_point_backwards (point, &worklist, &seen, map, model);
+ const supernode *snode = worklist.pop ();
+ process_supernode_backwards (*snode, &worklist, &seen, map, model);
}
}
}
- /* Worklist for walking forwards from address-taken points. */
+ /* Worklist for walking forwards from address-taken snodes. */
{
- auto_vec<function_point> worklist;
+ auto_vec<const supernode *> worklist;
point_set_t seen;
/* Add all uses of the decl to the worklist. */
- for (auto iter : m_points_taking_address)
+ for (auto iter : m_snodes_taking_address)
{
worklist.safe_push (iter);
- /* Add to m_points_needing_decl (now that we traversed
+ /* Add to m_snodes_needing_decl (now that we traversed
it above for the backward worklist). */
- m_points_needing_decl.add (iter);
+ m_snodes_needing_decl.add (iter);
}
/* Process worklist by walking forwards. */
log_scope s (logger, "processing worklist");
while (worklist.length () > 0)
{
- function_point point = worklist.pop ();
- process_point_forwards (point, &worklist, &seen, map);
+ const supernode *snode = worklist.pop ();
+ process_supernode_forwards (*snode, &worklist, &seen, map);
}
}
}
}
-/* Add POINT to *WORKLIST if the point is not already in *seen.
- Add newly seen points to *SEEN and to m_points_needing_name. */
+/* Add SNODE to *WORKLIST if the point is not already in *seen.
+ Add newly seen supernodes to *SEEN and to m_snodes_needing_name. */
void
-state_purge_per_decl::add_to_worklist (const function_point &point,
- auto_vec<function_point> *worklist,
+state_purge_per_decl::add_to_worklist (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
point_set_t *seen,
logger *logger)
{
LOG_FUNC (logger);
if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("point: '");
- point.print (logger->get_printer (), format (false));
- logger->log_partial ("' for worklist for %qE", m_decl);
- logger->end_log_line ();
- }
+ logger->log ("SN %i for worklist for %qE", snode.m_id, m_decl);
- gcc_assert (point.get_function () == &get_function ());
- if (point.get_from_edge ())
- gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);
+ gcc_assert (snode.get_function () == &get_function ());
- if (seen->contains (point))
+ if (seen->contains (&snode))
{
if (logger)
logger->log ("already seen for %qE", m_decl);
{
if (logger)
logger->log ("not seen; adding to worklist for %qE", m_decl);
- m_points_needing_decl.add (point);
- seen->add (point);
- worklist->safe_push (point);
+ m_snodes_needing_decl.add (&snode);
+ seen->add (&snode);
+ worklist->safe_push (&snode);
}
}
return false;
}
-/* Process POINT, popped from *WORKLIST.
- Iterate over predecessors of POINT, adding to *WORKLIST and *SEEN,
+/* Process SNODE, popped from *WORKLIST.
+ Iterate over predecessors of SNODE, adding to *WORKLIST and *SEEN,
until we get to a stmt that fully overwrites the decl. */
void
state_purge_per_decl::
-process_point_backwards (const function_point &point,
- auto_vec<function_point> *worklist,
- point_set_t *seen,
- const state_purge_map &map,
- const region_model &model)
+process_supernode_backwards (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
+ point_set_t *seen,
+ const state_purge_map &map,
+ const region_model &model)
{
logger *logger = map.get_logger ();
LOG_FUNC (logger);
if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("considering point: '");
- point.print (logger->get_printer (), format (false));
- logger->log_partial ("' for %qE", m_decl);
- logger->end_log_line ();
- }
- const supernode *snode = point.get_supernode ();
+ logger->log ("considering SN %i for %qE", snode.m_id, m_decl);
- switch (point.get_kind ())
+ for (auto in_edge : snode.m_preds)
{
- default:
- gcc_unreachable ();
-
- case PK_ORIGIN:
- break;
-
- case PK_BEFORE_SUPERNODE:
- {
- /* Add given pred to worklist. */
- if (point.get_from_edge ())
- {
- gcc_assert (point.get_from_edge ()->m_src);
- add_to_worklist
- (function_point::after_supernode (point.get_from_edge ()->m_src),
- worklist, seen, logger);
- }
- else
- {
- /* Add any intraprocedually edge for a call. */
- if (snode->m_returning_call)
- {
- gcall *returning_call = snode->m_returning_call;
- cgraph_edge *cedge
- = supergraph_call_edge (snode->m_fun,
- returning_call);
- if(cedge)
- {
- superedge *sedge
- = map.get_sg ().get_intraprocedural_edge_for_call (cedge);
- gcc_assert (sedge);
- add_to_worklist
- (function_point::after_supernode (sedge->m_src),
- worklist, seen, logger);
- }
- else
- {
- supernode *callernode
- = map.get_sg ().get_supernode_for_stmt (returning_call);
-
- gcc_assert (callernode);
- add_to_worklist
- (function_point::after_supernode (callernode),
- worklist, seen, logger);
- }
- }
- }
- }
- break;
+ if (logger)
+ logger->log ("considering edge from SN %i", in_edge->m_src->m_id);
- case PK_BEFORE_STMT:
- {
- /* This is somewhat equivalent to how the SSA case handles
- def-stmts. */
- if (fully_overwrites_p (point.get_stmt (), m_decl, model)
- /* ...but we mustn't be at a point that also consumes the
- current value of the decl when it's generating the new
- value, for cases such as
- struct st s;
- s = foo ();
- s = bar (s);
- where we want to make sure that we don't stop at the:
- s = bar (s);
- since otherwise we would erroneously purge the state of "s"
- after:
- s = foo ();
- */
- && !m_points_needing_decl.contains (point))
- {
- if (logger)
- logger->log ("stmt fully overwrites %qE; terminating", m_decl);
- return;
- }
- if (point.get_stmt_idx () > 0)
- add_to_worklist (function_point::before_stmt
- (snode, point.get_stmt_idx () - 1),
- worklist, seen, logger);
- else
+ bool fully_overwrites_decl = false;
+ if (auto op = in_edge->get_op ())
{
- /* Add before_supernode to worklist. This captures the in-edge,
- so we have to do it once per in-edge. */
- unsigned i;
- superedge *pred;
- FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
- add_to_worklist (function_point::before_supernode (snode,
- pred),
- worklist, seen, logger);
+ /* This is somewhat equivalent to how the SSA case handles
+ def-stmts. */
+ if (auto stmt = op->maybe_get_stmt ())
+ if (fully_overwrites_p (stmt, m_decl, model)
+ /* ...but we mustn't be at a point that also consumes the
+ current value of the decl when it's generating the new
+ value, for cases such as
+ struct st s;
+ s = foo ();
+ s = bar (s);
+ where we want to make sure that we don't stop at the:
+ s = bar (s);
+ since otherwise we would erroneously purge the state of "s"
+ after:
+ s = foo ();
+ */
+ && !m_snodes_needing_decl.contains (&snode))
+ fully_overwrites_decl = true;
}
- }
- break;
-
- case PK_AFTER_SUPERNODE:
- {
- if (snode->m_stmts.length ())
- add_to_worklist
- (function_point::before_stmt (snode,
- snode->m_stmts.length () - 1),
- worklist, seen, logger);
- else
- {
- /* Add before_supernode to worklist. This captures the in-edge,
- so we have to do it once per in-edge. */
- unsigned i;
- superedge *pred;
- FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
- add_to_worklist (function_point::before_supernode (snode,
- pred),
- worklist, seen, logger);
- }
- }
- break;
+ if (fully_overwrites_decl)
+ {
+ if (logger)
+ logger->log ("op fully overwrites %qE; terminating", m_decl);
+ }
+ else
+ add_to_worklist (*in_edge->m_src, worklist, seen, logger);
}
-
}
-/* Process POINT, popped from *WORKLIST.
- Iterate over successors of POINT, adding to *WORKLIST and *SEEN. */
+/* Process SNODE, popped from *WORKLIST.
+ Iterate over successors of SNODE, adding to *WORKLIST and *SEEN. */
void
state_purge_per_decl::
-process_point_forwards (const function_point &point,
- auto_vec<function_point> *worklist,
- point_set_t *seen,
- const state_purge_map &map)
+process_supernode_forwards (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
+ point_set_t *seen,
+ const state_purge_map &map)
{
logger *logger = map.get_logger ();
LOG_FUNC (logger);
if (logger)
- {
- logger->start_log_line ();
- logger->log_partial ("considering point: '");
- point.print (logger->get_printer (), format (false));
- logger->log_partial ("' for %qE", m_decl);
- logger->end_log_line ();
- }
- const supernode *snode = point.get_supernode ();
-
- switch (point.get_kind ())
- {
- default:
- case PK_ORIGIN:
- gcc_unreachable ();
-
- case PK_BEFORE_SUPERNODE:
- {
- function_point next = point.get_next ();
- add_to_worklist (next, worklist, seen, logger);
- }
- break;
+ logger->log ("considering SN %i for %qE", snode.m_id, m_decl);
- case PK_BEFORE_STMT:
- {
- /* Perhaps we should stop at a clobber of the decl,
- but that ought to purge state for us explicitly. */
- function_point next = point.get_next ();
- add_to_worklist (next, worklist, seen, logger);
- }
- break;
-
- case PK_AFTER_SUPERNODE:
- {
- /* Look at interprocedural out-edges. */
- unsigned i;
- superedge *succ;
- FOR_EACH_VEC_ELT (snode->m_succs, i, succ)
- {
- enum edge_kind kind = succ->get_kind ();
- if (kind == SUPEREDGE_CFG_EDGE
- || kind == SUPEREDGE_INTRAPROCEDURAL_CALL)
- add_to_worklist (function_point::before_supernode (succ->m_dest,
- succ),
- worklist, seen, logger);
- }
- }
- break;
- }
+ for (auto out_edge : snode.m_succs)
+ add_to_worklist (*out_edge->m_dest, worklist, seen, logger);
}
-/* Return true if the decl is needed at POINT. */
+/* Return true if the decl is needed at SNODE. */
bool
-state_purge_per_decl::needed_at_point_p (const function_point &point) const
+state_purge_per_decl::needed_at_supernode_p (const supernode *snode) const
{
- return const_cast <point_set_t &> (m_points_needing_decl).contains (point);
+ return const_cast <point_set_t &> (m_snodes_needing_decl).contains (snode);
}
+void
+state_purge_per_decl::on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode)
+{
+ if (m_snodes_needing_decl.contains (&old_snode))
+ m_snodes_needing_decl.add (&new_snode);
+ if (m_snodes_taking_address.contains (&old_snode))
+ m_snodes_taking_address.add (&new_snode);
+}
/* class state_purge_annotator : public dot_annotator. */
/* Implementation of dot_annotator::add_node_annotations vfunc for
Add an additional record showing which names are purged on entry
to the supernode N. */
-bool
-state_purge_annotator::add_node_annotations (graphviz_out *gv,
- const supernode &n,
- bool within_table) const
-{
- if (m_map == nullptr)
- return false;
-
- if (within_table)
- return false;
-
- pretty_printer *pp = gv->get_pp ();
-
- pp_printf (pp, "annotation_for_node_%i", n.m_index);
- pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
- "lightblue");
- pp_write_text_to_stream (pp);
-
- /* Different in-edges mean different names need purging.
- Determine which points to dump. */
- auto_vec<function_point> points;
- if (n.entry_p () || n.m_returning_call)
- points.safe_push (function_point::before_supernode (&n, nullptr));
- else
- for (auto inedge : n.m_preds)
- points.safe_push (function_point::before_supernode (&n, inedge));
- points.safe_push (function_point::after_supernode (&n));
-
- for (auto & point : points)
- {
- point.print (pp, format (true));
- pp_newline (pp);
- print_needed (gv, point, false);
- pp_newline (pp);
- }
-
- pp_string (pp, "\"];\n\n");
- pp_flush (pp);
- return false;
-}
-
-/* Print V to GV as a comma-separated list in braces, titling it with TITLE.
- If WITHIN_TABLE is true, print it within a <TR>
+/* Print V to GV as a comma-separated list in braces within a <TR>,
+ titling it with TITLE.
Subroutine of state_purge_annotator::print_needed. */
static void
print_vec_of_names (graphviz_out *gv, const char *title,
- const auto_vec<tree> &v, bool within_table)
+ const auto_vec<tree> &v)
{
pretty_printer *pp = gv->get_pp ();
tree name;
unsigned i;
- if (within_table)
- gv->begin_trtd ();
+ gv->begin_trtd ();
pp_printf (pp, "%s: {", title);
FOR_EACH_VEC_ELT (v, i, name)
{
pp_printf (pp, "%qE", name);
}
pp_printf (pp, "}");
- if (within_table)
- {
- pp_write_text_as_html_like_dot_to_stream (pp);
- gv->end_tdtr ();
- }
+ pp_write_text_as_html_like_dot_to_stream (pp);
+ gv->end_tdtr ();
pp_newline (pp);
}
-/* Implementation of dot_annotator::add_stmt_annotations for
- state_purge_annotator.
-
- Add text showing which names are purged at STMT. */
-
void
-state_purge_annotator::add_stmt_annotations (graphviz_out *gv,
- const gimple *stmt,
- bool within_row) const
+state_purge_annotator::add_node_annotations (graphviz_out *gv,
+ const supernode &snode) const
{
- if (within_row)
- return;
-
if (m_map == nullptr)
return;
- if (stmt->code == GIMPLE_PHI)
- return;
-
- pretty_printer *pp = gv->get_pp ();
-
- pp_newline (pp);
-
- const supernode *supernode = m_map->get_sg ().get_supernode_for_stmt (stmt);
- unsigned int stmt_idx = supernode->get_stmt_index (stmt);
- function_point before_stmt
- (function_point::before_stmt (supernode, stmt_idx));
-
- print_needed (gv, before_stmt, true);
-}
-
-/* Get the decls and SSA names needed and not-needed at POINT, and
- print them to GV.
- If WITHIN_TABLE is true, print them within <TR> elements. */
-
-void
-state_purge_annotator::print_needed (graphviz_out *gv,
- const function_point &point,
- bool within_table) const
-{
auto_vec<tree> needed;
auto_vec<tree> not_needed;
for (state_purge_map::ssa_iterator iter = m_map->begin_ssas ();
{
tree name = (*iter).first;
state_purge_per_ssa_name *per_name_data = (*iter).second;
- if (&per_name_data->get_function () == point.get_function ())
+ if (&per_name_data->get_function () == snode.get_function ())
{
- if (per_name_data->needed_at_point_p (point))
+ if (per_name_data->needed_at_supernode_p (&snode))
needed.safe_push (name);
else
not_needed.safe_push (name);
{
tree decl = (*iter).first;
state_purge_per_decl *per_decl_data = (*iter).second;
- if (&per_decl_data->get_function () == point.get_function ())
+ if (&per_decl_data->get_function () == snode.get_function ())
{
- if (per_decl_data->needed_at_point_p (point))
+ if (per_decl_data->needed_at_supernode_p (&snode))
needed.safe_push (decl);
else
not_needed.safe_push (decl);
}
}
- print_vec_of_names (gv, "needed here", needed, within_table);
- print_vec_of_names (gv, "not needed here", not_needed, within_table);
+ print_vec_of_names (gv, "needed here", needed);
+ print_vec_of_names (gv, "not needed here", not_needed);
}
#endif /* #if ENABLE_ANALYZER */
#ifndef GCC_ANALYZER_STATE_PURGE_H
#define GCC_ANALYZER_STATE_PURGE_H
-/* Hash traits for function_point. */
-
-template <> struct default_hash_traits<function_point>
-: public pod_hash_traits<function_point>
-{
- static const bool empty_zero_p = false;
-};
-
-template <>
-inline hashval_t
-pod_hash_traits<function_point>::hash (value_type v)
-{
- return v.hash ();
-}
-
-template <>
-inline bool
-pod_hash_traits<function_point>::equal (const value_type &existing,
- const value_type &candidate)
-{
- return existing == candidate;
-}
-template <>
-inline void
-pod_hash_traits<function_point>::mark_deleted (value_type &v)
-{
- v = function_point::deleted ();
-}
-template <>
-inline void
-pod_hash_traits<function_point>::mark_empty (value_type &v)
-{
- v = function_point::empty ();
-}
-template <>
-inline bool
-pod_hash_traits<function_point>::is_deleted (value_type v)
-{
- return v.get_kind () == PK_DELETED;
-}
-template <>
-inline bool
-pod_hash_traits<function_point>::is_empty (value_type v)
-{
- return v.get_kind () == PK_EMPTY;
-}
-
namespace ana {
/* The result of analyzing which decls and SSA names can be purged from state at
decl_iterator begin_decls () const { return m_decl_map.begin (); }
decl_iterator end_decls () const { return m_decl_map.end (); }
+ void
+ on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode);
+
private:
DISABLE_COPY_AND_ASSIGN (state_purge_map);
tree get_fndecl () const { return m_fun.decl; }
protected:
- typedef hash_set<function_point> point_set_t;
+ typedef hash_set<const supernode *> point_set_t;
state_purge_per_tree (const function &fun)
: m_fun (fun)
tree name,
const function &fun);
- bool needed_at_point_p (const function_point &point) const;
+ bool needed_at_supernode_p (const supernode *snode) const;
-private:
- static function_point before_use_stmt (const state_purge_map &map,
- const gimple *use_stmt);
+ void
+ on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode);
- void add_to_worklist (const function_point &point,
- auto_vec<function_point> *worklist,
+private:
+ void add_to_worklist (const supernode &node,
+ auto_vec<const supernode *> *worklist,
logger *logger);
- void process_point (const function_point &point,
- auto_vec<function_point> *worklist,
- const state_purge_map &map);
+ void process_supernode (const supernode &node,
+ auto_vec<const supernode *> *worklist,
+ const state_purge_map &map);
- point_set_t m_points_needing_name;
+ point_set_t m_snodes_needing_name;
tree m_name;
};
tree decl,
const function &fun);
- bool needed_at_point_p (const function_point &point) const;
+ bool needed_at_supernode_p (const supernode *snode) const;
- void add_needed_at (const function_point &point);
- void add_pointed_to_at (const function_point &point);
+ void add_needed_at (const supernode &snode);
+ void add_pointed_to_at (const supernode &snode);
void process_worklists (const state_purge_map &map,
region_model_manager *mgr);
+ void
+ on_duplicated_node (const supernode &old_snode,
+ const supernode &new_snode);
+
private:
- static function_point before_use_stmt (const state_purge_map &map,
+ static const supernode * before_use_stmt (const state_purge_map &map,
const gimple *use_stmt);
- void add_to_worklist (const function_point &point,
- auto_vec<function_point> *worklist,
+ void add_to_worklist (const supernode &node,
+ auto_vec<const supernode *> *worklist,
point_set_t *seen,
logger *logger);
- void process_point_backwards (const function_point &point,
- auto_vec<function_point> *worklist,
- point_set_t *seen,
- const state_purge_map &map,
- const region_model &model);
- void process_point_forwards (const function_point &point,
- auto_vec<function_point> *worklist,
- point_set_t *seen,
- const state_purge_map &map);
-
- point_set_t m_points_needing_decl;
- point_set_t m_points_taking_address;
+ void process_supernode_backwards (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
+ point_set_t *seen,
+ const state_purge_map &map,
+ const region_model &model);
+ void process_supernode_forwards (const supernode &snode,
+ auto_vec<const supernode *> *worklist,
+ point_set_t *seen,
+ const state_purge_map &map);
+
+ point_set_t m_snodes_needing_decl;
+ point_set_t m_snodes_taking_address;
tree m_decl;
};
public:
state_purge_annotator (const state_purge_map *map) : m_map (map) {}
- bool add_node_annotations (graphviz_out *gv, const supernode &n, bool)
- const final override;
-
- void add_stmt_annotations (graphviz_out *gv, const gimple *stmt,
- bool within_row)
- const final override;
+ void
+ add_node_annotations (graphviz_out *gv,
+ const supernode &n) const final override;
private:
- void print_needed (graphviz_out *gv,
- const function_point &point,
- bool within_table) const;
-
const state_purge_map *m_map;
};
}
}
+static bool
+needs_loop_replay_fixup_p (const svalue &sval)
+{
+ struct my_visitor : public visitor
+ {
+ my_visitor () : m_found_widening (false) {}
+
+ void
+ visit_widening_svalue (const widening_svalue *) final override
+ {
+ m_found_widening = true;
+ }
+
+ bool m_found_widening;
+ } v;
+
+ sval.accept (&v);
+ return v.m_found_widening;
+}
+
/* Subroutine for use by exploded_path::feasible_p.
We need to deal with state differences between:
{
const binding_key *key = (*bind_iter).m_key;
const svalue *sval = (*bind_iter).m_sval;
- if (sval->get_kind () == SK_WIDENING)
+ if (needs_loop_replay_fixup_p (*sval))
{
binding_cluster *this_cluster
= get_or_create_cluster (*mgr->get_store_manager (),
--- /dev/null
+/* Fixing up location_t values of supernodes.
+ Copyright (C) 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/>. */
+
+#define INCLUDE_DEQUE
+#include "analyzer/common.h"
+
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/supergraph-manipulation.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+namespace {
+
+class location_fixer
+{
+public:
+ location_fixer (supergraph &sg,
+ ana::logger *logger)
+ : m_sg (sg),
+ m_logger (logger),
+ m_stats ()
+ {
+ for (auto node : sg.m_nodes)
+ if (node->m_loc == UNKNOWN_LOCATION)
+ m_worklist.ensure_node_queued (node, logger);
+ }
+
+ /* High level ops. */
+
+ supernode *
+ pop_next_node_in_queue ()
+ {
+ return m_worklist.pop ();
+ }
+
+ void
+ consider_node (supernode *node)
+ {
+ m_stats.m_num_iterations++;
+
+ log_nesting_level sentinel (m_logger, "considering SN: %i", node->m_id);
+
+ /* Already have a location for this node. */
+ if (useful_location_p (node->m_loc))
+ return;
+
+ /* For snodes with UNKNOWN_LOCATION with a single in-edge, try to
+ propagate the location from it. */
+ if (node->m_preds.length () == 1)
+ {
+ auto in_edge = node->m_preds[0];
+ if (useful_location_p (in_edge->m_src->m_loc))
+ {
+ node->m_loc = in_edge->m_src->m_loc;
+ m_stats.m_num_copies++;
+ if (m_logger)
+ m_logger->log ("copying location 0x%lx from SN %i to SN %i",
+ node->m_loc,
+ in_edge->m_src->m_id, node->m_id);
+ for (auto out_edge : node->m_succs)
+ m_worklist.ensure_node_queued (out_edge->m_dest, m_logger);
+ }
+ }
+ }
+
+private:
+ supergraph &m_sg;
+ state_purge_map *m_purge_map;
+ supergraph_manipulation::worklist m_worklist;
+ ana::logger *m_logger;
+ struct stats
+ {
+ stats () = default;
+
+ void log (ana::logger &logger)
+ {
+ logger.log ("# iterations taken: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_iterations);
+ logger.log ("# locations copied: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_copies);
+ }
+
+ size_t m_num_iterations;
+ size_t m_num_copies;
+
+ } m_stats;
+};
+
+} // anonymous namespace
+
+void
+supergraph::fixup_locations (logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ location_fixer opt (*this, logger);
+ while (supernode *node = opt.pop_next_node_in_queue ())
+ opt.consider_node (node);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
--- /dev/null
+/* Classes for manipulating the supergraph.
+ Copyright (C) 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_SUPERGRAPH_MANIPULATION_H
+#define GCC_ANALYZER_SUPERGRAPH_MANIPULATION_H
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+namespace supergraph_manipulation {
+
+class worklist
+{
+public:
+ worklist ()
+ : m_indices ()
+ {
+ bitmap_clear (m_indices);
+ }
+
+ void ensure_node_queued (supernode *node, ana::logger *logger)
+ {
+ if (bitmap_bit_p (m_indices, node->m_id))
+ return; // already in queue
+ if (logger)
+ logger->log ("queued SN: %i", node->m_id);
+ m_queue.push_back (node);
+ bitmap_set_bit (m_indices, node->m_id);
+ }
+
+ supernode *pop ()
+ {
+ if (m_queue.empty ())
+ return nullptr;
+ supernode *node = m_queue.front ();
+ m_queue.pop_front ();
+ bitmap_clear_bit (m_indices, node->m_id);
+ return node;
+ }
+
+private:
+ // The queue
+ std::deque<supernode *> m_queue;
+
+ /* Indices of all nodes in the queue, so
+ we can lazily add them in constant time. */
+ auto_bitmap m_indices;
+};
+
+
+} // namespace ana::supergraph_manipulation
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#endif /* GCC_ANALYZER_SUPERGRAPH_MANIPULATION_H */
--- /dev/null
+/* Simplifying the supergraph.
+ Copyright (C) 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/>. */
+
+#define INCLUDE_DEQUE
+#define INCLUDE_SET
+#include "analyzer/common.h"
+
+#include "cgraph.h"
+
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/supergraph-manipulation.h"
+#include "analyzer/bar-chart.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+namespace {
+
+/* A class for tracking a set of simplifications to a supergraph.
+ supergraph.m_nodes and supergraph.m_edges may contain deleted object
+ during the lifetime of this class; they are removed at the end. */
+
+class simplifier
+{
+public:
+ simplifier (supergraph &sg,
+ ana::logger *logger)
+ : m_sg (sg),
+ m_nodes_to_remove (),
+ m_edges_to_remove (),
+ m_logger (logger),
+ m_stats ()
+ {
+ for (auto node : sg.m_nodes)
+ m_worklist.ensure_node_queued (node, logger);
+ }
+
+ ~simplifier ()
+ {
+ // Remove nodes from m_sg.m_nodes and delete them
+ m_sg.delete_nodes (m_nodes_to_remove);
+
+ // Remove edges from m_sg.m_edges and delete them
+ {
+ unsigned read_index, write_index;
+ superedge **elem_ptr;
+ VEC_ORDERED_REMOVE_IF (m_sg.m_edges, read_index, write_index, elem_ptr,
+ edge_to_be_removed_p (*elem_ptr));
+ for (auto iter : m_edges_to_remove)
+ delete iter;
+ }
+
+ if (m_logger)
+ {
+ log_nesting_level sentinel (m_logger, "stats");
+ m_stats.log (*m_logger);
+ }
+ }
+
+ /* Manipulations: logged, and refreshing the worklist. */
+
+ void
+ set_edge_dest (superedge *edge, supernode *new_dest)
+ {
+ if (edge->m_dest == new_dest)
+ return;
+ log_nesting_level sentinel
+ (m_logger, "updating edge dest: SN: %i -> SN: {%i, %i}",
+ edge->m_src->m_id,
+ edge->m_dest->m_id,
+ new_dest->m_id);
+ m_worklist.ensure_node_queued (edge->m_src, m_logger);
+ m_worklist.ensure_node_queued (edge->m_dest, m_logger);
+ m_worklist.ensure_node_queued (new_dest, m_logger);
+
+ edge->set_dest (new_dest);
+
+ m_stats.m_num_set_edge_dest++;
+ }
+
+ void
+ remove_node (supernode *node)
+ {
+ log_nesting_level sentinel
+ (m_logger, "removing node: SN: %i", node->m_id);
+ for (auto in_edge : node->m_preds)
+ remove_edge (in_edge);
+ for (auto out_edge : node->m_succs)
+ remove_edge (out_edge);
+ m_nodes_to_remove.insert (node);
+ m_stats.m_num_remove_node++;
+ }
+
+ void
+ remove_edge (superedge *edge)
+ {
+ log_nesting_level sentinel
+ (m_logger, "removing edge dest: SN: %i -> SN: %i",
+ edge->m_src->m_id,
+ edge->m_dest->m_id);
+ gcc_assert (!edge->preserve_p ());
+
+ m_worklist.ensure_node_queued (edge->m_src, m_logger);
+ m_worklist.ensure_node_queued (edge->m_dest, m_logger);
+
+ edge->m_src->remove_out_edge (edge);
+ edge->m_dest->remove_in_edge (edge);
+
+ m_edges_to_remove.insert (edge);
+
+ m_stats.m_num_remove_edge++;
+ }
+
+ /* High level ops. */
+
+ supernode *
+ pop_next_node_in_queue ()
+ {
+ return m_worklist.pop ();
+ }
+
+ void
+ consider_node (supernode *node)
+ {
+ m_stats.m_num_iterations++;
+
+ log_nesting_level sentinel (m_logger, "considering SN: %i", node->m_id);
+
+ /* Eliminate nodes with no in-edges that aren't function entry nodes. */
+ if (node->m_preds.length () == 0
+ && !node->entry_p ())
+ {
+ log_nesting_level s2 (m_logger, "no in-edges");
+ remove_node (node);
+ return;
+ }
+
+ /* Handle nodes with a single out-edge. */
+ if (node->m_succs.length () == 1)
+ if (consider_single_out_edge (node, node->m_succs[0]))
+ return;
+ }
+
+ bool
+ consider_single_out_edge (supernode *node,
+ superedge *single_out_edge)
+ {
+ if (node->preserve_p ())
+ {
+ log_nesting_level s3
+ (m_logger,
+ "node has preserve_p flag; preserving");
+ return false;
+ }
+ if (single_out_edge->preserve_p ())
+ {
+ log_nesting_level s3
+ (m_logger,
+ "edge has preserve_p flag; preserving");
+ return false;
+ }
+
+ /* Is the single out-edge a no-op? */
+ if (!single_out_edge->get_op ())
+ {
+ /* If the node doesn't add useful location information, we can
+ redirect all in-edges to the node to point at the outedge's dst. */
+ log_nesting_level s2 (m_logger,
+ "single outedge is no-op (to SN: %i)",
+ node->m_succs[0]->m_dest->m_id);
+ bool redirect = false;
+ if (node->m_loc == UNKNOWN_LOCATION)
+ {
+ log_nesting_level s3
+ (m_logger,
+ "node is at UNKNOWN_LOCATION; redirecting in-edges");
+ redirect = true;
+ }
+ else if (node->m_loc == single_out_edge->m_dest->m_loc)
+ {
+ log_nesting_level s3
+ (m_logger,
+ "node has same location as successor; redirecting in-edges");
+ redirect = true;
+ }
+
+ if (redirect)
+ {
+ for (auto in_edge : node->m_preds)
+ set_edge_dest (in_edge, single_out_edge->m_dest);
+ return true;
+ }
+
+ return false;
+ }
+
+ gcc_assert (single_out_edge->get_op ());
+
+ return false;
+ }
+
+private:
+ bool edge_to_be_removed_p (superedge *edge)
+ {
+ return m_edges_to_remove.find (edge) != m_edges_to_remove.end ();
+ }
+
+ supergraph &m_sg;
+ supergraph_manipulation::worklist m_worklist;
+ std::set<supernode *> m_nodes_to_remove;
+ std::set<superedge *> m_edges_to_remove;
+ ana::logger *m_logger;
+ struct stats
+ {
+ stats () = default;
+
+ void log (ana::logger &logger)
+ {
+ logger.log ("# iterations taken: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_iterations);
+ logger.log ("# nodes removed: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_remove_node);
+ logger.log ("# edges removed: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_remove_edge);
+ logger.log ("# set_edge_dest: " HOST_SIZE_T_PRINT_UNSIGNED,
+ (fmt_size_t)m_num_set_edge_dest);
+ }
+
+ size_t m_num_iterations;
+ size_t m_num_remove_node;
+ size_t m_num_remove_edge;
+ size_t m_num_set_edge_dest;
+
+ } m_stats;
+};
+
+} // anonymous namespace
+
+void
+supergraph::log_stats (logger *logger) const
+{
+ if (!logger)
+ return;
+
+ logger->log ("# nodes: %u", m_nodes.length ());
+ logger->log ("# edges: %u", m_edges.length ());
+
+ /* Show per-function bar charts of supernodes per function. */
+ {
+ bar_chart snodes_per_function;
+ logger->log ("snodes per function:");
+
+ cgraph_node *cgnode;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (cgnode)
+ {
+ function *fn = cgnode->get_fun ();
+
+ int snodes_for_this_function = 0;
+ for (auto node : m_nodes)
+ if (node->get_function () == fn)
+ ++snodes_for_this_function;
+
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%qD", fn->decl);
+ snodes_per_function.add_item (pp_formatted_text (&pp),
+ snodes_for_this_function);
+ }
+
+ snodes_per_function.print (logger->get_printer ());
+ }
+}
+
+void
+supergraph::simplify (logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ {
+ log_nesting_level sentinel (logger, "before simplification:");
+ log_stats (logger);
+ }
+
+ {
+ simplifier opt (*this, logger);
+ while (supernode *node = opt.pop_next_node_in_queue ())
+ opt.consider_node (node);
+ }
+
+ {
+ log_nesting_level sentinel (logger, "after simplification");
+ log_stats (logger);
+ }
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
--- /dev/null
+/* Sorting the nodes in the supergraph.
+ Copyright (C) 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/>. */
+
+#define INCLUDE_SET
+#include "analyzer/common.h"
+
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-logging.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Update m_nodes to be ORDERING.
+ Update the m_id of all nodes to reflect the new orderding.
+ This assumes that all nodes are in ORDERING; does not delete
+ any underlying nodes. */
+
+void
+supergraph::reorder_nodes_and_ids (const std::vector<supernode *> &ordering,
+ logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ m_nodes.truncate (0);
+
+ for (size_t new_id = 0; new_id < ordering.size (); ++new_id)
+ {
+ supernode *snode = ordering[new_id];
+ if (logger)
+ {
+ if ((size_t)snode->m_id == new_id)
+ logger->log ("SN %i is unchanged", snode->m_id);
+ else
+ logger->log ("old SN %i is now SN %li", snode->m_id, new_id);
+ }
+ m_nodes.safe_push (snode);
+ snode->m_id = new_id;
+ }
+
+ m_next_snode_id = m_nodes.length ();
+}
+
+/* std::set::contains is C++20 onwards. */
+template <typename T>
+static bool
+set_contains_p (const std::set<T> &s, T v)
+{
+ return s.find (v) != s.end ();
+}
+
+namespace {
+
+class sorting_worklist
+{
+public:
+ sorting_worklist ()
+ : m_queue (key_t (*this, nullptr))
+ {
+ }
+
+ void add_node (supernode *n);
+ supernode *take_next (logger *logger);
+ bool contains_p (supernode *n) const;
+
+private:
+ class key_t
+ {
+ public:
+ key_t (const sorting_worklist &w, supernode *snode)
+ : m_worklist (w), m_snode (snode)
+ {}
+
+ bool operator< (const key_t &other) const
+ {
+ return cmp (*this, other) < 0;
+ }
+
+ bool operator== (const key_t &other) const
+ {
+ return cmp (*this, other) == 0;
+ }
+
+ bool operator> (const key_t &other) const
+ {
+ return !(*this == other || *this < other);
+ }
+
+ private:
+ static int cmp (const key_t &ka, const key_t &kb);
+ const sorting_worklist &m_worklist;
+ supernode *m_snode;
+ };
+
+ bool
+ already_seen_all_predecessors_p (const supernode *n,
+ logger *logger) const;
+
+ std::set<supernode *> m_set_of_ordered_nodes;
+ std::set<supernode *> m_set_of_queued_nodes;
+ typedef fibonacci_heap<key_t, supernode> queue_t;
+ queue_t m_queue;
+};
+
+void
+sorting_worklist::add_node (supernode *n)
+{
+ m_queue.insert ({*this, n}, n);
+ m_set_of_queued_nodes.insert (n);
+}
+
+supernode *
+sorting_worklist::take_next (logger *logger)
+{
+ if (m_queue.empty ())
+ return nullptr;
+
+ std::vector<supernode *> rejected;
+
+ /* First, try to find a node for which all predecessors
+ have been ordered. */
+ while (!m_queue.empty ())
+ {
+ supernode *candidate = m_queue.extract_min ();
+
+ // n shouldn't be already within the ordering
+ gcc_assert (!set_contains_p (m_set_of_ordered_nodes, candidate));
+
+ if (logger)
+ logger->log ("consider SN %i from worklist", candidate->m_id);
+
+ if (already_seen_all_predecessors_p (candidate, logger))
+ {
+ if (logger)
+ logger->log ("all predecessors of SN %i seen; using it",
+ candidate->m_id);
+ for (auto r : rejected)
+ add_node (r);
+ m_set_of_ordered_nodes.insert (candidate);
+ m_set_of_queued_nodes.erase (candidate);
+ return candidate;
+ }
+ else
+ rejected.push_back (candidate);
+ }
+
+ /* Otherwise, simply use the first node. */
+ for (auto r : rejected)
+ add_node (r);
+ supernode *n = m_queue.extract_min ();
+ if (logger)
+ logger->log ("using first in queue: SN %i", n->m_id);
+ m_set_of_ordered_nodes.insert (n);
+ m_set_of_queued_nodes.erase (n);
+ return n;
+}
+
+bool
+sorting_worklist::contains_p (supernode *n) const
+{
+ return (m_set_of_queued_nodes.find (n) != m_set_of_queued_nodes.end ()
+ || m_set_of_ordered_nodes.find (n) != m_set_of_ordered_nodes.end ());
+}
+
+int
+sorting_worklist::key_t::cmp (const key_t &ka, const key_t &kb)
+{
+ const supernode *snode_a = ka.m_snode;
+ const supernode *snode_b = kb.m_snode;
+
+ /* Sort by BB. */
+ if (int bb_cmp = snode_a->m_bb->index - snode_b->m_bb->index)
+ return bb_cmp;
+
+ /* Sort by existing id. */
+ return snode_a->m_id - snode_b->m_id;
+}
+
+bool
+sorting_worklist::already_seen_all_predecessors_p (const supernode *n,
+ logger *logger) const
+{
+ for (auto e : n->m_preds)
+ if (!set_contains_p (m_set_of_ordered_nodes, e->m_src))
+ {
+ if (logger)
+ logger->log ("not yet ordered predecessor SN %i", e->m_src->m_id);
+ return false;
+ }
+ return true;
+}
+
+static std::vector<supernode *>
+get_node_ordering (const supergraph &sg,
+ logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ std::vector<supernode *> ordering_vec;
+
+ cgraph_node *cgnode;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (cgnode)
+ {
+ function *fun = cgnode->get_fun ();
+
+ sorting_worklist worklist;
+
+ supernode *start_node = sg.get_node_for_function_entry (*fun);
+ worklist.add_node (start_node);
+
+ // Find the best node to add next in the ordering
+ while (supernode *next = worklist.take_next (logger))
+ {
+ gcc_assert (next);
+ if (logger)
+ logger->log ("next: SN: %i", next->m_id);
+
+ ordering_vec.push_back (next);
+
+ for (auto out_edge : next->m_succs)
+ {
+ supernode *dest_node = out_edge->m_dest;
+ if (!worklist.contains_p (dest_node))
+ worklist.add_node (dest_node);
+ }
+ }
+ }
+
+ return ordering_vec;
+}
+
+} // anonymous namespace
+
+void
+supergraph::sort_nodes (logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ const std::vector<supernode *> ordering = get_node_ordering (*this, logger);
+ reorder_nodes_and_ids (ordering, logger);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
+#define INCLUDE_DEQUE
#include "analyzer/common.h"
#include "timevar.h"
#include "cfg.h"
#include "digraph.h"
#include "tree-cfg.h"
-#include "tree-dfa.h"
#include "cfganal.h"
#include "except.h"
+#include "diagnostics/file-cache.h"
+
#include "analyzer/supergraph.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/region-model.h"
+#include "analyzer/exploded-graph.h"
#if ENABLE_ANALYZER
return ultimate_node->get_fun ();
}
-/* Get the cgraph_edge, but only if there's an underlying function body. */
-
-cgraph_edge *
-supergraph_call_edge (function *fun, const gimple *stmt)
-{
- const gcall *call = dyn_cast<const gcall *> (stmt);
- if (!call)
- return nullptr;
- cgraph_edge *edge
- = cgraph_node::get (fun->decl)->get_edge (const_cast <gimple *> (stmt));
- if (!edge)
- return nullptr;
- if (!edge->callee)
- return nullptr; /* e.g. for a function pointer. */
- if (!get_ultimate_function_for_cgraph_edge (edge))
- return nullptr;
- return edge;
-}
-
/* class saved_uids.
In order to ensure consistent results without relying on the ordering
pair->first->uid = pair->second;
}
+/* When building the supergraph, should STMT be handled
+ along each out-edge in the CFG, or as separate superedge
+ "within" the BB. */
+
+static bool
+control_flow_stmt_p (const gimple &stmt)
+{
+ switch (gimple_code (&stmt))
+ {
+ case GIMPLE_COND:
+ case GIMPLE_EH_DISPATCH:
+ case GIMPLE_GOTO:
+ case GIMPLE_SWITCH:
+ return true;
+
+ case GIMPLE_ASM:
+ case GIMPLE_ASSIGN:
+ case GIMPLE_CALL:
+ case GIMPLE_DEBUG:
+ case GIMPLE_LABEL:
+ case GIMPLE_NOP:
+ case GIMPLE_PREDICT:
+ case GIMPLE_RESX:
+ case GIMPLE_RETURN:
+ return false;
+
+ /* We don't expect to see any other statement kinds in the analyzer. */
+ default:
+ internal_error ("unexpected gimple stmt code: %qs",
+ gimple_code_name[gimple_code (&stmt)]);
+ break;
+ }
+}
+
/* supergraph's ctor. Walk the callgraph, building supernodes for each
- CFG basic block, splitting the basic blocks at callsites. Join
- together the supernodes with interprocedural and intraprocedural
- superedges as appropriate.
+ CFG basic block, splitting the basic blocks at statements. Join
+ together the supernodes with interprocedural superedges as appropriate.
Assign UIDs to the gimple stmts. */
-supergraph::supergraph (logger *logger)
+supergraph::supergraph (region_model_manager &mgr,
+ logger *logger)
+: m_next_snode_id (0)
{
auto_timevar tv (TV_ANALYZER_SUPERGRAPH);
LOG_FUNC (logger);
+ /* For each BB, if present, the stmt that terminates it. */
+ typedef ordered_hash_map<basic_block, gimple *> bb_to_stmt_t;
+ bb_to_stmt_t control_stmt_ending_bbs;
+
/* First pass: make supernodes (and assign UIDs to the gimple stmts). */
{
/* Sort the cgraph_nodes? */
cgraph_node *node;
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
- {
- function *fun = node->get_fun ();
-
- /* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
- the supergraph (by doing it per-function). */
- auto_cfun sentinel (fun);
- mark_dfs_back_edges ();
+ {
+ function *fun = node->get_fun ();
- const int start_idx = m_nodes.length ();
+ log_nesting_level log_sentinel (logger, "function: %qD", fun->decl);
- basic_block bb;
- FOR_ALL_BB_FN (bb, fun)
- {
- /* The initial supernode for the BB gets the phi nodes (if any). */
- supernode *node_for_stmts
- = add_node (fun, bb, nullptr, phi_nodes (bb));
- m_bb_to_initial_node.put (bb, node_for_stmts);
- for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi);
- gsi_next (&gpi))
- {
- gimple *stmt = gsi_stmt (gpi);
- m_stmt_to_node_t.put (stmt, node_for_stmts);
- m_stmt_uids.make_uid_unique (stmt);
- }
+ /* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
+ the supergraph (by doing it per-function). */
+ auto_cfun cfun_sentinel (fun);
+ mark_dfs_back_edges ();
- /* Append statements from BB to the current supernode, splitting
- them into a new supernode at each call site; such call statements
- appear in both supernodes (representing call and return). */
- gimple_stmt_iterator gsi;
- for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
- {
- gimple *stmt = gsi_stmt (gsi);
- /* Discard debug stmts here, so we don't have to check for
- them anywhere within the analyzer. */
- if (is_gimple_debug (stmt))
- continue;
- node_for_stmts->m_stmts.safe_push (stmt);
- m_stmt_to_node_t.put (stmt, node_for_stmts);
- m_stmt_uids.make_uid_unique (stmt);
- if (cgraph_edge *edge = supergraph_call_edge (fun, stmt))
- {
- m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts);
- node_for_stmts = add_node (fun, bb, as_a <gcall *> (stmt),
- nullptr);
- m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts);
- }
- else
- {
- // maybe call is via a function pointer
- if (gcall *call = dyn_cast<gcall *> (stmt))
- {
- cgraph_edge *edge
- = cgraph_node::get (fun->decl)->get_edge (stmt);
- if (!edge || !edge->callee)
- {
- supernode *old_node_for_stmts = node_for_stmts;
- node_for_stmts = add_node (fun, bb, call, nullptr);
-
- superedge *sedge
- = new callgraph_superedge (old_node_for_stmts,
- node_for_stmts,
- SUPEREDGE_INTRAPROCEDURAL_CALL,
- nullptr);
- add_edge (sedge);
- }
- }
- }
- }
+ const int start_id = m_nodes.length ();
- m_bb_to_final_node.put (bb, node_for_stmts);
- }
+ basic_block bb;
+ FOR_ALL_BB_FN (bb, fun)
+ if (gimple *final_control_stmt
+ = populate_for_basic_block (bb, fun, logger))
+ control_stmt_ending_bbs.put (bb, final_control_stmt);
- const unsigned num_snodes = m_nodes.length () - start_idx;
- m_function_to_num_snodes.put (fun, num_snodes);
+ const unsigned num_snodes = m_nodes.length () - start_id;
+ m_function_to_num_snodes.put (fun, num_snodes);
- if (logger)
- {
- const int end_idx = m_nodes.length () - 1;
- logger->log ("SN: %i...%i: function %qD",
- start_idx, end_idx, fun->decl);
- }
- }
+ if (logger)
+ {
+ const int end_id = m_nodes.length () - 1;
+ logger->log ("SN: %i...%i: function %qD",
+ start_id, end_id, fun->decl);
+ }
+ }
}
- /* Second pass: make superedges. */
+ /* Second pass: make superedges between basic blocks. */
{
/* Make superedges for CFG edges. */
for (bb_to_node_t::iterator iter = m_bb_to_final_node.begin ();
basic_block bb = (*iter).first;
supernode *src_supernode = (*iter).second;
+ gimple *control_stmt_ending_bb = nullptr;
+ if (auto control_stmt_iter = control_stmt_ending_bbs.get (bb))
+ control_stmt_ending_bb = *control_stmt_iter;
+
::edge cfg_edge;
int idx;
if (bb->succs)
basic_block dest_cfg_block = cfg_edge->dest;
supernode *dest_supernode
= *m_bb_to_initial_node.get (dest_cfg_block);
- cfg_superedge *cfg_sedge
- = add_cfg_edge (src_supernode, dest_supernode, cfg_edge);
- m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
+ add_sedges_for_cfg_edge (src_supernode,
+ dest_supernode,
+ cfg_edge,
+ control_stmt_ending_bb,
+ mgr,
+ logger);
}
}
+ }
+}
+
+/* Create a run of supernodes and superedges for the BB within FUN
+ expressing all of the stmts apart from the final control flow stmt (if any).
+ Return the control stmt that ends this bb, if any. */
- /* Make interprocedural superedges for calls. */
+gimple *
+supergraph::populate_for_basic_block (basic_block bb,
+ function *fun,
+ logger *logger)
+{
+ log_nesting_level sentinel (logger, "bb %i", bb->index);
+
+ supernode *initial_snode_in_bb = add_node (fun, bb, logger);
+ m_bb_to_initial_node.put (bb, initial_snode_in_bb);
+
+ if (bb->index == ENTRY_BLOCK)
+ /* Use the decl's location, rather than fun->function_start_locus,
+ which leads to more readable output. */
+ initial_snode_in_bb->m_loc = DECL_SOURCE_LOCATION (fun->decl);
+ else if (bb->index == EXIT_BLOCK)
+ initial_snode_in_bb->m_loc = fun->function_end_locus;
+ else if (gsi_end_p (gsi_start_bb (bb)))
{
- for (cgraph_edge_to_node_t::iterator iter
- = m_cgraph_edge_to_caller_prev_node.begin ();
- iter != m_cgraph_edge_to_caller_prev_node.end ();
- ++iter)
+ /* BB has no stmts, and isn't the ENTRY or EXIT node.
+ Try to find a source location for it. */
+ if (bb->succs->length () == 1)
{
- cgraph_edge *edge = (*iter).first;
- supernode *caller_prev_supernode = (*iter).second;
- function* callee_fn = get_ultimate_function_for_cgraph_edge (edge);
- if (!callee_fn || !callee_fn->cfg)
- continue;
- basic_block callee_cfg_block = ENTRY_BLOCK_PTR_FOR_FN (callee_fn);
- supernode *callee_supernode
- = *m_bb_to_initial_node.get (callee_cfg_block);
- call_superedge *sedge
- = add_call_superedge (caller_prev_supernode,
- callee_supernode,
- edge);
- m_cgraph_edge_to_call_superedge.put (edge, sedge);
+ auto outedge = (*bb->succs)[0];
+ if (useful_location_p (outedge->goto_locus))
+ {
+ /* We have an empty basic block with one out-edge,
+ perhaps part of an empty infinite loop. */
+ if (logger)
+ logger->log ("using location 0x%lx from outedge",
+ outedge->goto_locus);
+ initial_snode_in_bb->m_loc = outedge->goto_locus;
+ }
}
}
- /* Make interprocedural superedges for returns. */
+ initial_snode_in_bb->m_state_merger_node = true;
+
+ gimple *final_control_flow_stmt = nullptr;
+
+ /* Create a run of supernodes for the stmts in BB,
+ connected by stmt_superedge. */
+ gimple_stmt_iterator gsi;
+ supernode *prev_snode_in_bb = initial_snode_in_bb;
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
{
- for (cgraph_edge_to_node_t::iterator iter
- = m_cgraph_edge_to_caller_next_node.begin ();
- iter != m_cgraph_edge_to_caller_next_node.end ();
- ++iter)
+ gimple *next_stmt = gsi_stmt (gsi);
+
+ if (logger)
{
- cgraph_edge *edge = (*iter).first;
- supernode *caller_next_supernode = (*iter).second;
- function* callee_fn = get_ultimate_function_for_cgraph_edge (edge);
- if (!callee_fn || !callee_fn->cfg)
+ logger->start_log_line ();
+ logger->log_partial ("next_stmt: ");
+ pp_gimple_stmt_1 (logger->get_printer (), next_stmt,
+ 0, (dump_flags_t)0);
+ logger->end_log_line ();
+ }
+ prev_snode_in_bb->m_loc = get_stmt_location (next_stmt, fun);
+ prev_snode_in_bb->m_stmt_loc = next_stmt->location;
+ m_node_for_stmt.insert ({next_stmt, prev_snode_in_bb});
+
+ m_stmt_uids.make_uid_unique (next_stmt);
+
+ if (auto glabel_ = dyn_cast<const glabel *>(next_stmt))
+ {
+ /* Associate the GIMPLE_LABEL with its snode. */
+ prev_snode_in_bb->m_label = gimple_label_label (glabel_);
+
+ /* Only create an snode for the label if it has location
+ information. */
+ if (glabel_->location == UNKNOWN_LOCATION)
continue;
- basic_block callee_cfg_block = EXIT_BLOCK_PTR_FOR_FN (callee_fn);
- supernode *callee_supernode
- = *m_bb_to_initial_node.get (callee_cfg_block);
- return_superedge *sedge
- = add_return_superedge (callee_supernode,
- caller_next_supernode,
- edge);
- m_cgraph_edge_to_return_superedge.put (edge, sedge);
}
- }
- /* Make intraprocedural superedges linking the two halves of a call. */
- {
- for (cgraph_edge_to_node_t::iterator iter
- = m_cgraph_edge_to_caller_prev_node.begin ();
- iter != m_cgraph_edge_to_caller_prev_node.end ();
- ++iter)
+ // handle control flow stmts on the edges
+ if (control_flow_stmt_p (*next_stmt))
{
- cgraph_edge *edge = (*iter).first;
- supernode *caller_prev_supernode = (*iter).second;
- supernode *caller_next_supernode
- = *m_cgraph_edge_to_caller_next_node.get (edge);
+ final_control_flow_stmt = next_stmt;
+ break;
+ }
+
+ supernode *snode_after_next_stmt = add_node (fun, bb, logger);
+ if (prev_snode_in_bb)
+ {
+ std::unique_ptr<operation> op;
+ switch (gimple_code (next_stmt))
+ {
+ default:
+ gcc_unreachable ();
+ break;
+ case GIMPLE_ASM:
+ op = std::make_unique<gasm_op>
+ (*as_a <const gasm *> (next_stmt));
+ break;
+ case GIMPLE_ASSIGN:
+ op = std::make_unique<gassign_op>
+ (*as_a <const gassign *> (next_stmt));
+ break;
+ case GIMPLE_CALL:
+ op = call_and_return_op::make
+ (*as_a <const gcall *> (next_stmt));
+ break;
+ case GIMPLE_PREDICT:
+ op = std::make_unique<predict_op> (*next_stmt);
+ break;
+ case GIMPLE_RESX:
+ op = std::make_unique<resx_op>
+ (*as_a <const gresx *> (next_stmt));
+ break;
+ case GIMPLE_RETURN:
+ op = std::make_unique<greturn_op>
+ (*as_a <const greturn *> (next_stmt));
+ break;
+
+ case GIMPLE_DEBUG:
+ case GIMPLE_LABEL:
+ case GIMPLE_NOP:
+ /* Treat all of these as no-ops within analyzer; though
+ perhaps we care about their locations. */
+ break;
+ }
+
superedge *sedge
- = new callgraph_superedge (caller_prev_supernode,
- caller_next_supernode,
- SUPEREDGE_INTRAPROCEDURAL_CALL,
- edge);
+ = new superedge (prev_snode_in_bb,
+ snode_after_next_stmt,
+ std::move (op),
+ nullptr);
add_edge (sedge);
- m_cgraph_edge_to_intraproc_superedge.put (edge, sedge);
}
-
+ prev_snode_in_bb = snode_after_next_stmt;
}
- }
+
+ m_bb_to_final_node.put (bb, prev_snode_in_bb);
+
+ return final_control_flow_stmt;
}
/* supergraph's dtor. Reset stmt uids. */
{
function *fun = node->get_fun ();
gcc_assert (fun);
+ auto_cfun sentinel (fun);
+
const char *funcname = function_name (fun);
gv.println ("subgraph \"cluster_%s\" {",
funcname);
" label=\"%s\";\n"),
funcname);
- /* Break out the nodes into clusters by BB from original CFG. */
- {
- basic_block bb;
- FOR_ALL_BB_FN (bb, fun)
- {
- if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
- {
- gv.println ("subgraph \"cluster_%s_bb_%i\" {",
- funcname, bb->index);
- gv.indent ();
- pp_printf (pp,
- ("style=\"dashed\";"
- " color=\"black\";"
- " label=\"bb: %i\";\n"),
- bb->index);
- }
-
- // TODO: maybe keep an index per-function/per-bb to speed this up???
- int i;
- supernode *n;
- FOR_EACH_VEC_ELT (m_nodes, i, n)
- if (n->m_fun == fun && n->m_bb == bb)
- n->dump_dot (&gv, dump_args);
-
- if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
- {
- /* Terminate per-bb "subgraph" */
- gv.outdent ();
- gv.println ("}");
- }
- }
- }
-
- /* Add an invisible edge from ENTRY to EXIT, to improve the graph layout. */
- pp_string (pp, "\t");
- get_node_for_function_entry (*fun)->dump_dot_id (pp);
- pp_string (pp, ":s -> ");
- get_node_for_function_exit (*fun)->dump_dot_id (pp);
- pp_string (pp, ":n [style=\"invis\",constraint=true];\n");
+ if (loops_for_fn (fun))
+ dump_dot_to_gv_for_loop (gv, dump_args, get_loop (fun, 0), fun);
+ else
+ {
+ basic_block bb;
+ FOR_ALL_BB_FN (bb, fun)
+ dump_dot_to_gv_for_bb (gv, dump_args, bb, fun);
+ }
/* Terminate per-function "subgraph" */
gv.outdent ();
FOR_EACH_VEC_ELT (m_edges, i, e)
e->dump_dot (&gv, dump_args);
+ if (dump_args.m_node_annotator)
+ dump_args.m_node_annotator->add_extra_objects (&gv);
+
/* Terminate "digraph" */
gv.outdent ();
gv.println ("}");
}
+/* Recursively dump all the snodes within LOOP and the loops
+ within it. */
+
+void
+supergraph::dump_dot_to_gv_for_loop (graphviz_out &gv,
+ const dump_args_t &dump_args,
+ class loop *loop,
+ function *fun) const
+{
+ pretty_printer *pp = gv.get_pp ();
+
+ basic_block *body;
+ unsigned int i;
+ // Adapted from graph.cc:draw_cfg_nodes_for_loop
+ const char *fillcolors[3] = { "grey88", "grey77", "grey66" };
+
+ if (loop->header != NULL
+ && loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
+ pp_printf (pp,
+ "\tsubgraph cluster_%d_%d {\n"
+ "\tstyle=\"filled\";\n"
+ "\tcolor=\"darkgreen\";\n"
+ "\tfillcolor=\"%s\";\n"
+ "\tlabel=\"loop %d\";\n"
+ "\tlabeljust=l;\n"
+ "\tpenwidth=2;\n",
+ fun->funcdef_no, loop->num,
+ fillcolors[(loop_depth (loop) - 1) % 3],
+ loop->num);
+
+ // Recurse
+ for (class loop *inner = loop->inner; inner; inner = inner->next)
+ dump_dot_to_gv_for_loop (gv, dump_args, inner, fun);
+
+ if (loop->header == NULL)
+ return;
+
+ if (loop->latch == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ body = get_loop_body (loop);
+ else
+ body = get_loop_body_in_bfs_order (loop);
+
+ for (i = 0; i < loop->num_nodes; i++)
+ {
+ basic_block bb = body[i];
+ if (bb->loop_father == loop)
+ dump_dot_to_gv_for_bb (gv, dump_args, bb, fun);
+ }
+
+ free (body);
+
+ if (loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
+ pp_printf (pp, "\t}\n");
+}
+
+/* Dump all the snodes from BB. */
+
+void
+supergraph::dump_dot_to_gv_for_bb (graphviz_out &gv,
+ const dump_args_t &dump_args,
+ basic_block bb,
+ function *fun) const
+{
+ pretty_printer *pp = gv.get_pp ();
+
+ if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
+ {
+ const char *funcname = function_name (fun);
+ gv.println ("subgraph \"cluster_%s_bb_%i\" {",
+ funcname, bb->index);
+ gv.indent ();
+ pp_printf (pp,
+ ("style=\"dashed\";"
+ " color=\"black\";"
+ " label=\"bb: %i\";\n"),
+ bb->index);
+ }
+
+ // TODO: maybe keep an index per-function/per-bb to speed this up???
+ int i;
+ supernode *n;
+ FOR_EACH_VEC_ELT (m_nodes, i, n)
+ if (n->m_fun == fun && n->m_bb == bb)
+ n->dump_dot (&gv, dump_args);
+
+ if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
+ {
+ /* Terminate per-bb "subgraph" */
+ gv.outdent ();
+ gv.println ("}");
+ }
+}
+
+
/* Dump this graph in .dot format to FP, using DUMP_ARGS. */
void
return sgraph_obj;
}
-/* Create a supernode for BB within FUN and add it to this supergraph.
-
- If RETURNING_CALL is non-NULL, the supernode represents the resumption
- of the basic block after returning from that call.
-
- If PHI_NODES is non-NULL, this is the initial supernode for the basic
- block, and is responsible for any handling of the phi nodes. */
+/* Create a supernode within BB within FUN and add it to this supergraph. */
supernode *
-supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
- gimple_seq phi_nodes)
+supergraph::add_node (function *fun, basic_block bb, logger *logger)
{
- supernode *n = new supernode (fun, bb, returning_call, phi_nodes,
- m_nodes.length ());
+ supernode *n = new supernode (fun, bb, m_next_snode_id++);
+ m_snode_by_id.push_back (n);
m_nodes.safe_push (n);
+ if (logger)
+ logger->log ("created SN %i", n->m_id);
return n;
}
-/* Create a new cfg_superedge from SRC to DEST for the underlying CFG edge E,
- adding it to this supergraph.
-
- If the edge is for a switch or eh_dispatch statement, create a
- switch_cfg_superedge or eh_dispatch_cfg_superedge subclass,
- respectively */
-
-cfg_superedge *
-supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e)
+void
+supergraph::delete_nodes (const std::set<supernode *> &snodes_to_delete)
{
- /* Special-case switch and eh_dispatch edges. */
- gimple *stmt = src->get_last_stmt ();
- std::unique_ptr<cfg_superedge> new_edge;
- if (stmt && stmt->code == GIMPLE_SWITCH)
- new_edge = std::make_unique<switch_cfg_superedge> (src, dest, e);
- else if (stmt && stmt->code == GIMPLE_EH_DISPATCH)
- new_edge = eh_dispatch_cfg_superedge::make (src, dest, e,
- as_a <geh_dispatch *> (stmt));
- else
- new_edge = std::make_unique<cfg_superedge> (src, dest, e);
- add_edge (new_edge.get ());
- return new_edge.release ();
+ /* Remove nodes from m_nodes. */
+ unsigned read_index, write_index;
+ supernode **elem_ptr;
+ VEC_ORDERED_REMOVE_IF
+ (m_nodes, read_index, write_index, elem_ptr,
+ snodes_to_delete.find (*elem_ptr) != snodes_to_delete.end ()
+ );
+
+ /* Remove nodes from m_snode_by_id, and delete them. */
+ for (auto iter : snodes_to_delete)
+ {
+ gcc_assert (iter->m_preds.length () == 0);
+ gcc_assert (iter->m_succs.length () == 0);
+ m_snode_by_id[iter->m_id] = nullptr;
+ delete iter;
+ }
}
-/* Create and add a call_superedge representing an interprocedural call
- from SRC to DEST, using CEDGE. */
+/* Create a chain of nodes and edges in this supergraph from SRC to DEST
+ to handle CFG_EDGE in the underlying CFG:
+
+ +---+
+ |SRC|
+ +---+
+ |
+ | optional edge for ctrlflow_stmt (if CTRLFLOW_STMT is non-null)
+ | (e.g. checking conditions on a GIMPLE_COND)
+ |
+ V
+ +------+
+ |(node)|
+ +------+
+ |
+ | optional edge for any phi nodes in the destination basic block
+ |
+ V
+ +------+
+ |(node)|
+ +------+
+ |
+ | optional edge for state merging at CFG join points
+ |
+ V
+ +----+
+ |DEST|
+ +----+
+
+ adding nodes where necessary between the edges, and adding a no-op edge
+ for the case where there is no CTRLFLOW_STMT, phi nodes, or
+ state merging. */
-call_superedge *
-supergraph::add_call_superedge (supernode *src, supernode *dest,
- cgraph_edge *cedge)
+void
+supergraph::add_sedges_for_cfg_edge (supernode *src,
+ supernode *dest,
+ ::edge cfg_edge,
+ gimple *ctrlflow_stmt,
+ region_model_manager &mgr,
+ logger *logger)
{
- call_superedge *new_edge = new call_superedge (src, dest, cedge);
- add_edge (new_edge);
- return new_edge;
-}
+ log_nesting_level sentinel (logger,
+ "edge: bb %i -> bb %i",
+ cfg_edge->src->index,
+ cfg_edge->dest->index);
+ std::unique_ptr<operation> ctrlflow_op;
+ if (ctrlflow_stmt)
+ switch (gimple_code (ctrlflow_stmt))
+ {
+ default:
+ gcc_unreachable ();
+ break;
+ case GIMPLE_COND:
+ ctrlflow_op = std::make_unique<gcond_edge_op>
+ (cfg_edge,
+ *as_a <gcond *> (ctrlflow_stmt));
+ break;
+ case GIMPLE_EH_DISPATCH:
+ ctrlflow_op
+ = eh_dispatch_edge_op::make (src, dest,
+ cfg_edge,
+ *as_a <geh_dispatch *> (ctrlflow_stmt));
+ break;
+ case GIMPLE_GOTO:
+ {
+ ctrlflow_op = std::make_unique<ggoto_edge_op>
+ (cfg_edge,
+ *as_a <ggoto *> (ctrlflow_stmt),
+ dest->m_label);
+ }
+ break;
+ case GIMPLE_SWITCH:
+ ctrlflow_op = std::make_unique<switch_case_op>
+ (*src->get_function (),
+ cfg_edge,
+ *as_a <gswitch *> (ctrlflow_stmt),
+ *mgr.get_range_manager ());
+ break;
+ }
+ else
+ {
+ if (cfg_edge->flags & EDGE_ABNORMAL)
+ /* Don't create superedges for such CFG edges (though
+ computed gotos are handled by the GIMPLE_GOTO clause above). */
+ return;
+ }
-/* Create and add a return_superedge representing returning from an
- interprocedural call, returning from SRC to DEST, using CEDGE. */
+ /* Determine a location to use for any snodes within the CFG edge. */
+ location_t dest_loc = dest->m_loc;
+ if (useful_location_p (cfg_edge->goto_locus))
+ dest_loc = cfg_edge->goto_locus;
+
+ /* If the dest is a control flow join point, then for each CFG in-edge
+ add an extra snode/sedge before DEST and route to it.
+ We hope this will help state-merging keep the
+ different in-edges separately. */
+ if (cfg_edge->dest->preds->length () > 1
+ && cfg_edge->dest->index != EXIT_BLOCK)
+ {
+ auto extra_snode = add_node (src->get_function (),
+ cfg_edge->dest,
+ logger);
+ extra_snode->m_loc = dest_loc;
+ extra_snode->m_preserve_p = true;
+ extra_snode->m_state_merger_node = true;
+ add_edge (new superedge (extra_snode, dest, nullptr, nullptr));
+ dest = extra_snode;
+ }
-return_superedge *
-supergraph::add_return_superedge (supernode *src, supernode *dest,
- cgraph_edge *cedge)
-{
- return_superedge *new_edge = new return_superedge (src, dest, cedge);
- add_edge (new_edge);
- return new_edge;
+ std::unique_ptr<operation> phi_op;
+ if (phi_nodes (cfg_edge->dest))
+ phi_op = phis_for_edge_op::maybe_make (cfg_edge);
+ if (phi_op)
+ {
+ superedge *edge_for_phis_op;
+ if (ctrlflow_op)
+ {
+ /* We have two ops; add an edge for each:
+ SRC --{ctrlflow_op}--> before_phi_nodes --{phi_op}--> DEST. */
+ supernode *before_phi_nodes
+ = add_node (src->get_function (), cfg_edge->src, logger);
+ before_phi_nodes->m_loc = dest_loc;
+ add_edge (new superedge (src, before_phi_nodes,
+ std::move (ctrlflow_op),
+ cfg_edge));
+ edge_for_phis_op = new superedge (before_phi_nodes, dest,
+ std::move (phi_op),
+ cfg_edge);
+ }
+ else
+ /* We just have a phi_op; add: SRC --{phi_op}--> DEST. */
+ edge_for_phis_op
+ = new superedge (src, dest, std::move (phi_op), cfg_edge);
+ add_edge (edge_for_phis_op);
+ m_edges_for_phis.insert ({cfg_edge, edge_for_phis_op});
+ }
+ else
+ /* We don't have a phi op, create this edge:
+ SRC --{ctrlflow_op}--> DEST
+ where ctrlflow_op might be nullptr (for a no-op edge). */
+ add_edge (new superedge (src, dest, std::move (ctrlflow_op), cfg_edge));
}
+// class supernode : public dnode<supergraph_traits>
+
/* Implementation of dnode::dump_dot vfunc for supernodes.
Write a cluster for the node, and within it a .dot node showing
void
supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const
{
- gv->println ("subgraph cluster_node_%i {",
- m_index);
- gv->indent ();
-
- gv->println("style=\"solid\";");
- gv->println("color=\"black\";");
- gv->println("fillcolor=\"lightgrey\";");
- gv->println("label=\"sn: %i (bb: %i)\";", m_index, m_bb->index);
-
pretty_printer *pp = gv->get_pp ();
- if (args.m_node_annotator)
- args.m_node_annotator->add_node_annotations (gv, *this, false);
-
gv->write_indent ();
dump_dot_id (pp);
+
pp_printf (pp,
- " [shape=none,margin=0,style=filled,fillcolor=%s,label=<",
- "lightgrey");
+ " [shape=none,margin=0,style=filled,label=<");
pp_string (pp, "<TABLE BORDER=\"0\">");
pp_write_text_to_stream (pp);
- bool had_row = false;
+ pp_printf (pp, "<TR><TD>sn: %i (bb: %i)",
+ m_id, m_bb->index);
+ if (args.m_eg)
+ pp_printf (pp, "; scc: %i", args.m_eg->get_scc_id (*this));
+ pp_string (pp, "</TD></TR>");
+ pp_newline (pp);
- /* Give any annotator the chance to add its own per-node TR elements. */
- if (args.m_node_annotator)
- if (args.m_node_annotator->add_node_annotations (gv, *this, true))
- had_row = true;
-
- if (m_returning_call)
+ if (m_preserve_p)
{
- gv->begin_trtd ();
- pp_string (pp, "returning call: ");
- gv->end_tdtr ();
-
- gv->begin_tr ();
- gv->begin_td ();
- pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
- pp_write_text_as_html_like_dot_to_stream (pp);
- gv->end_td ();
- /* Give any annotator the chance to add per-stmt TD elements to
- this row. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, m_returning_call,
- true);
- gv->end_tr ();
-
- /* Give any annotator the chance to add per-stmt TR elements. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, m_returning_call,
- false);
+ pp_string (pp, "<TR><TD>(preserve)</TD></TR>");
+ pp_newline (pp);
+ }
+ if (m_state_merger_node)
+ {
+ pp_string (pp, "<TR><TD BGCOLOR=\"#ec7a08\">"
+ "<FONT COLOR=\"white\">STATE MERGER</FONT></TD></TR>");
pp_newline (pp);
-
- had_row = true;
}
-
if (entry_p ())
{
pp_string (pp, "<TR><TD>ENTRY</TD></TR>");
pp_newline (pp);
- had_row = true;
}
-
- if (return_p ())
+ else if (exit_p ())
{
pp_string (pp, "<TR><TD>EXIT</TD></TR>");
pp_newline (pp);
- had_row = true;
}
- /* Phi nodes. */
- for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
+ /* Source location. */
+ /* Highlight nodes where we're missing source location information.
+ Ideally this all gets fixed up by supergraph::fixup_locations. */
+ if (m_loc == UNKNOWN_LOCATION)
+ pp_string (pp, "<TR><TD>UNKNOWN_LOCATION</TD></TR>");
+ else if (get_pure_location (m_loc) == UNKNOWN_LOCATION)
{
- const gimple *stmt = gsi_stmt (gpi);
- gv->begin_tr ();
- gv->begin_td ();
- pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
- pp_write_text_as_html_like_dot_to_stream (pp);
- gv->end_td ();
- /* Give any annotator the chance to add per-phi TD elements to
- this row. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, stmt, true);
- gv->end_tr ();
-
- /* Give any annotator the chance to add per-phi TR elements. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, stmt, false);
-
- pp_newline (pp);
- had_row = true;
+ pp_printf (pp, "<TR><TD>location: 0x%lx</TD></TR>", m_loc);
+ pp_string (pp, "<TR><TD>UNKNOWN_LOCATION</TD></TR>");
}
-
- /* Statements. */
- int i;
- gimple *stmt;
- FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+ else
{
- gv->begin_tr ();
- gv->begin_td ();
- pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
- pp_write_text_as_html_like_dot_to_stream (pp);
- gv->end_td ();
- /* Give any annotator the chance to add per-stmt TD elements to
- this row. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, stmt, true);
- gv->end_tr ();
-
- /* Give any annotator the chance to add per-stmt TR elements. */
- if (args.m_node_annotator)
- args.m_node_annotator->add_stmt_annotations (gv, stmt, false);
-
- pp_newline (pp);
- had_row = true;
+ /* Show the source location, but skip it for the case where it's
+ the same as all previous snodes (and there's a single in-edge). */
+ bool show_location = true;
+ location_t prev_loc = UNKNOWN_LOCATION;
+ if (m_preds.length () == 1)
+ {
+ prev_loc = m_preds[0]->m_src->m_loc;
+ if (prev_loc == m_loc)
+ show_location = false;
+ }
+ if (show_location)
+ {
+ pp_printf (pp, "<TR><TD>location: 0x%lx</TD></TR>", m_loc);
+
+ /* Print changes to the expanded location (or all of it if
+ we have multiple in-edges). */
+ auto prev_exploc = expand_location (prev_loc);
+ auto exploc = expand_location (m_loc);
+
+ if ((exploc.file != prev_exploc.file)
+ && exploc.file)
+ pp_printf (pp, "<TR><TD>%s:%i:</TD></TR>",
+ exploc.file, exploc.line);
+ if (exploc.line != prev_exploc.line)
+ if (const diagnostics::char_span line
+ = global_dc->get_file_cache ().get_source_line (exploc.file,
+ exploc.line))
+ {
+ /* Print the source line. */
+ pp_string (pp, "<TR><TD>");
+ pp_string (pp, "<TABLE BORDER=\"0\"><TR>");
+
+ // Line number:
+ pp_printf (pp, ("<TD ALIGN=\"RIGHT\" BGCOLOR=\"#0088ce\">"
+ "<FONT COLOR=\"white\"> %i</FONT></TD>"),
+ exploc.line);
+
+ // Line contents:
+ pp_string (pp, "<TD ALIGN=\"LEFT\" BGCOLOR=\"white\">");
+ pp_flush (pp);
+ for (size_t i = 0; i < line.length (); ++i)
+ pp_character (pp, line[i]);
+ pp_write_text_as_html_like_dot_to_stream (pp);
+ pp_string (pp, "</TD>");
+
+ pp_string (pp, "</TR></TABLE>");
+ pp_string (pp, "</TD></TR>");
+ }
+ }
}
- /* Give any annotator the chance to add additional per-node TR elements
- to the end of the TABLE. */
+ pp_flush (pp);
+
if (args.m_node_annotator)
- if (args.m_node_annotator->add_after_node_annotations (gv, *this))
- had_row = true;
+ args.m_node_annotator->add_node_annotations (gv, *this);
- /* Graphviz requires a TABLE element to have at least one TR
- (and each TR to have at least one TD). */
- if (!had_row)
- {
- pp_string (pp, "<TR><TD>(empty)</TD></TR>");
- pp_newline (pp);
- }
+ pp_printf (pp, "<TR><TD>m_stmt_loc: 0x%lx</TD></TR>", m_stmt_loc);
pp_string (pp, "</TABLE>>];\n\n");
pp_flush (pp);
-
- /* Terminate "subgraph" */
- gv->outdent ();
- gv->println ("}");
}
/* Write an ID for this node to PP, for use in .dot output. */
void
supernode::dump_dot_id (pretty_printer *pp) const
{
- pp_printf (pp, "node_%i", m_index);
+ pp_printf (pp, "node_%i", m_id);
}
/* Return a new json::object of the form
- {"idx": int,
+ {"id": int,
"fun": optional str
- "bb_idx": int,
- "returning_call": optional str,
- "phis": [str],
- "stmts" : [str]}. */
+ "bb_idx": int}. */
std::unique_ptr<json::object>
supernode::to_json () const
{
auto snode_obj = std::make_unique<json::object> ();
- snode_obj->set_integer ("idx", m_index);
+ snode_obj->set_integer ("id", m_id);
snode_obj->set_integer ("bb_idx", m_bb->index);
if (function *fun = get_function ())
snode_obj->set_string ("fun", function_name (fun));
- if (m_returning_call)
- {
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- pp_gimple_stmt_1 (&pp, m_returning_call, 0, (dump_flags_t)0);
- snode_obj->set_string ("returning_call", pp_formatted_text (&pp));
- }
-
- /* Phi nodes. */
- {
- auto phi_arr = std::make_unique<json::array> ();
- for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
- !gsi_end_p (gpi); gsi_next (&gpi))
- {
- const gimple *stmt = gsi_stmt (gpi);
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- pp_gimple_stmt_1 (&pp, stmt, 0, (dump_flags_t)0);
- phi_arr->append_string (pp_formatted_text (&pp));
- }
- snode_obj->set ("phis", std::move (phi_arr));
- }
-
- /* Statements. */
- {
- auto stmt_arr = std::make_unique<json::array> ();
- int i;
- gimple *stmt;
- FOR_EACH_VEC_ELT (m_stmts, i, stmt)
- {
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- pp_gimple_stmt_1 (&pp, stmt, 0, (dump_flags_t)0);
- stmt_arr->append_string (pp_formatted_text (&pp));
- }
- snode_obj->set ("stmts", std::move (stmt_arr));
- }
-
return snode_obj;
}
-/* Get a location_t for the start of this supernode. */
-
-location_t
-supernode::get_start_location () const
-{
- if (m_returning_call
- && get_pure_location (m_returning_call->location) != UNKNOWN_LOCATION)
- return m_returning_call->location;
-
- int i;
- gimple *stmt;
- FOR_EACH_VEC_ELT (m_stmts, i, stmt)
- if (get_pure_location (stmt->location) != UNKNOWN_LOCATION)
- return stmt->location;
-
- if (entry_p ())
- {
- // TWEAK: show the decl instead; this leads to more readable output:
- return DECL_SOURCE_LOCATION (m_fun->decl);
-
- return m_fun->function_start_locus;
- }
- if (return_p ())
- return m_fun->function_end_locus;
-
- /* We have no locations for stmts. If we have a single out-edge that's
- a CFG edge, the goto_locus of that edge is a better location for this
- than UNKNOWN_LOCATION. */
- if (m_succs.length () == 1)
- if (const cfg_superedge *cfg_sedge = m_succs[0]->dyn_cast_cfg_superedge ())
- return cfg_sedge->get_goto_locus ();
-
- return UNKNOWN_LOCATION;
-}
-
-/* Get a location_t for the end of this supernode. */
-
-location_t
-supernode::get_end_location () const
-{
- int i;
- gimple *stmt;
- FOR_EACH_VEC_ELT_REVERSE (m_stmts, i, stmt)
- if (get_pure_location (stmt->location) != UNKNOWN_LOCATION)
- return stmt->location;
-
- if (m_returning_call
- && get_pure_location (m_returning_call->location) != UNKNOWN_LOCATION)
- return m_returning_call->location;
-
- if (entry_p ())
- return m_fun->function_start_locus;
- if (return_p ())
- return m_fun->function_end_locus;
-
- /* If we have a single out-edge that's a CFG edge, use the goto_locus of
- that edge. */
- if (m_succs.length () == 1)
- if (const cfg_superedge *cfg_sedge = m_succs[0]->dyn_cast_cfg_superedge ())
- return cfg_sedge->get_goto_locus ();
-
- return UNKNOWN_LOCATION;
-}
-
-/* Given STMT within this supernode, return its index within m_stmts. */
-
-unsigned int
-supernode::get_stmt_index (const gimple *stmt) const
-{
- unsigned i;
- gimple *iter_stmt;
- FOR_EACH_VEC_ELT (m_stmts, i, iter_stmt)
- if (iter_stmt == stmt)
- return i;
- gcc_unreachable ();
-}
-
-/* Get any label_decl for this supernode, or NULL_TREE if there isn't one. */
-
-tree
-supernode::get_label () const
-{
- if (m_stmts.length () == 0)
- return NULL_TREE;
- const glabel *label_stmt = dyn_cast<const glabel *> (m_stmts[0]);
- if (!label_stmt)
- return NULL_TREE;
- return gimple_label_label (label_stmt);
-}
-
-/* Get a string for PK. */
-
-static const char *
-edge_kind_to_string (enum edge_kind kind)
-{
- switch (kind)
- {
- default:
- gcc_unreachable ();
- case SUPEREDGE_CFG_EDGE:
- return "SUPEREDGE_CFG_EDGE";
- case SUPEREDGE_CALL:
- return "SUPEREDGE_CALL";
- case SUPEREDGE_RETURN:
- return "SUPEREDGE_RETURN";
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- return "SUPEREDGE_INTRAPROCEDURAL_CALL";
- }
-};
-
/* Dump this superedge to PP. */
void
superedge::dump (pretty_printer *pp) const
{
- pp_printf (pp, "edge: SN: %i -> SN: %i", m_src->m_index, m_dest->m_index);
+ pp_printf (pp, "edge: SN: %i -> SN: %i", m_src->m_id, m_dest->m_id);
label_text desc (get_description (false));
if (strlen (desc.get ()) > 0)
{
int weight = 10;
const char *constraint = "true";
- switch (m_kind)
- {
- default:
- gcc_unreachable ();
- case SUPEREDGE_CFG_EDGE:
- break;
- case SUPEREDGE_CALL:
- color = "red";
- break;
- case SUPEREDGE_RETURN:
- color = "green";
- break;
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- style = "\"dotted\"";
- break;
- }
-
/* Adapted from graph.cc:draw_cfg_node_succ_edges. */
if (::edge cfg_edge = get_any_cfg_edge ())
{
m_dest->dump_dot_id (pp);
pp_printf (pp,
(" [style=%s, color=%s, weight=%d, constraint=%s,"
- " ltail=\"cluster_node_%i\", lhead=\"cluster_node_%i\""
" headlabel=\""),
- style, color, weight, constraint,
- m_src->m_index, m_dest->m_index);
+ style, color, weight, constraint);
+ pp_flush (pp);
dump_label_to_pp (pp, false);
+ pp_write_text_as_dot_label_to_stream (pp, false);
pp_printf (pp, "\"];\n");
}
/* Return a new json::object of the form
- {"kind" : str,
- "src_idx": int, the index of the source supernode,
- "dst_idx": int, the index of the destination supernode,
- "desc" : str. */
+ {"src_id": int, the index of the source supernode,
+ "dst_id": int, the index of the destination supernode} */
std::unique_ptr<json::object>
superedge::to_json () const
{
auto sedge_obj = std::make_unique<json::object> ();
- sedge_obj->set_string ("kind", edge_kind_to_string (m_kind));
- sedge_obj->set_integer ("src_idx", m_src->m_index);
- sedge_obj->set_integer ("dst_idx", m_dest->m_index);
-
- {
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- dump_label_to_pp (&pp, false);
- sedge_obj->set_string ("desc", pp_formatted_text (&pp));
- }
-
+ sedge_obj->set_integer ("src_id", m_src->m_id);
+ sedge_obj->set_integer ("dst_id", m_dest->m_id);
return sedge_obj;
}
-/* If this is an intraprocedural superedge, return the associated
- CFG edge. Otherwise, return nullptr. */
-
-::edge
-superedge::get_any_cfg_edge () const
-{
- if (const cfg_superedge *sub = dyn_cast_cfg_superedge ())
- return sub->get_cfg_edge ();
- return nullptr;
-}
+/* Return true iff this edge needs to be preserved during simplification. */
-/* If this is an interprocedural superedge, return the associated
- cgraph_edge *. Otherwise, return nullptr. */
-
-cgraph_edge *
-superedge::get_any_callgraph_edge () const
+bool
+superedge::preserve_p () const
{
- if (const callgraph_superedge *sub = dyn_cast_callgraph_superedge ())
- return sub->m_cedge;
- return nullptr;
+ if (m_cfg_edge)
+ if (m_cfg_edge->flags & (EDGE_EH | EDGE_DFS_BACK))
+ {
+ /* We use EDGE_EH in get_eh_outedge, and EDGE_DFS_BACK
+ for detecting infinite loops. */
+ return true;
+ }
+ return false;
}
/* Build a description of this superedge (e.g. "true" for the true
return label_text::take (xstrdup (pp_formatted_text (&pp)));
}
-/* Implementation of superedge::dump_label_to_pp for non-switch CFG
- superedges.
-
- For true/false edges, print "true" or "false" to PP.
-
- If USER_FACING is false, also print flags on the underlying CFG edge to
- PP. */
-
void
-cfg_superedge::dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const
+superedge::dump_label_to_pp (pretty_printer *pp, bool user_facing) const
{
- if (true_value_p ())
- pp_printf (pp, "true");
- else if (false_value_p ())
- pp_printf (pp, "false");
+ if (get_op ())
+ get_op ()->print_as_edge_label (pp, user_facing);
+ else
+ pp_printf (pp, "no-op");
if (user_facing)
return;
- /* Express edge flags as a string with " | " separator.
- e.g. " (flags FALLTHRU | DFS_BACK)". */
- if (get_flags ())
+ if (::edge cfg_edge = get_any_cfg_edge ())
{
- pp_string (pp, " (flags ");
- bool seen_flag = false;
+ if (cfg_edge->flags)
+ {
+ pp_string (pp, " (flags ");
+ bool seen_flag = false;
#define DEF_EDGE_FLAG(NAME,IDX) \
- do { \
- if (get_flags () & EDGE_##NAME) \
- { \
- if (seen_flag) \
- pp_string (pp, " | "); \
- pp_printf (pp, "%s", (#NAME)); \
- seen_flag = true; \
- } \
- } while (0);
+ do { \
+ if (cfg_edge->flags & EDGE_##NAME) \
+ { \
+ if (seen_flag) \
+ pp_string (pp, " | "); \
+ pp_printf (pp, "%s", (#NAME)); \
+ seen_flag = true; \
+ } \
+ } while (0);
#include "cfg-flags.def"
#undef DEF_EDGE_FLAG
- pp_string (pp, ")");
- }
-
- if (m_cfg_edge->goto_locus > BUILTINS_LOCATION)
- pp_string (pp, " (has goto_locus)");
-
- /* Otherwise, no label. */
-}
-
-/* Get the index number for this edge for use in phi stmts
- in its destination. */
-
-size_t
-cfg_superedge::get_phi_arg_idx () const
-{
- return m_cfg_edge->dest_idx;
-}
-
-/* Get the phi argument for PHI for this CFG edge. */
-
-tree
-cfg_superedge::get_phi_arg (const gphi *phi) const
-{
- size_t index = get_phi_arg_idx ();
- return gimple_phi_arg_def (phi, index);
-}
-
-/* class switch_cfg_superedge : public cfg_superedge. */
-
-switch_cfg_superedge::switch_cfg_superedge (supernode *src,
- supernode *dst,
- ::edge e)
-: cfg_superedge (src, dst, e)
-{
- /* Populate m_case_labels with all cases which go to DST. */
- const gswitch *gswitch = get_switch_stmt ();
- for (unsigned i = 0; i < gimple_switch_num_labels (gswitch); i++)
- {
- tree case_ = gimple_switch_label (gswitch, i);
- basic_block bb = label_to_block (src->get_function (),
- CASE_LABEL (case_));
- if (bb == dst->m_bb)
- m_case_labels.safe_push (case_);
- }
-}
-
-/* Implementation of superedge::dump_label_to_pp for CFG superedges for
- "switch" statements.
-
- Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP. */
-
-void
-switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
- bool user_facing ATTRIBUTE_UNUSED) const
-{
- if (user_facing)
- {
- for (unsigned i = 0; i < m_case_labels.length (); ++i)
- {
- if (i > 0)
- pp_string (pp, ", ");
- tree case_label = m_case_labels[i];
- gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
- tree lower_bound = CASE_LOW (case_label);
- tree upper_bound = CASE_HIGH (case_label);
- if (lower_bound)
- {
- pp_printf (pp, "case ");
- dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
- if (upper_bound)
- {
- pp_printf (pp, " ... ");
- dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
- false);
- }
- pp_printf (pp, ":");
- }
- else
- pp_printf (pp, "default:");
- }
- }
- else
- {
- pp_character (pp, '{');
- for (unsigned i = 0; i < m_case_labels.length (); ++i)
- {
- if (i > 0)
- pp_string (pp, ", ");
- tree case_label = m_case_labels[i];
- gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
- tree lower_bound = CASE_LOW (case_label);
- tree upper_bound = CASE_HIGH (case_label);
- if (lower_bound)
- {
- if (upper_bound)
- {
- pp_character (pp, '[');
- dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0,
- false);
- pp_string (pp, ", ");
- dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
- false);
- pp_character (pp, ']');
- }
- else
- dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
- }
- else
- pp_printf (pp, "default");
- }
- pp_character (pp, '}');
- if (implicitly_created_default_p ())
- {
- pp_string (pp, " IMPLICITLY CREATED");
+ pp_string (pp, ")");
}
+ if (cfg_edge->goto_locus > BUILTINS_LOCATION)
+ pp_printf (pp, " (has goto_locus: 0x%lx)", cfg_edge->goto_locus);
}
}
-/* Return true iff this edge is purely for an implicitly-created "default". */
-
bool
-switch_cfg_superedge::implicitly_created_default_p () const
-{
- if (m_case_labels.length () != 1)
- return false;
-
- tree case_label = m_case_labels[0];
- gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
- if (CASE_LOW (case_label))
- return false;
-
- /* We have a single "default" case.
- Assume that it was implicitly created if it has UNKNOWN_LOCATION. */
- return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION;
-}
-
-/* class eh_dispatch_cfg_superedge : public cfg_superedge. */
-
-/* Given an ERT_TRY region, get the eh_catch corresponding to
- the label of DST_SNODE, if any. */
-
-static eh_catch
-get_catch (eh_region eh_reg, supernode *dst_snode)
+superedge::supports_bulk_merge_p () const
{
- gcc_assert (eh_reg->type == ERT_TRY);
-
- tree dst_snode_label = dst_snode->get_label ();
- if (!dst_snode_label)
- return nullptr;
-
- for (eh_catch iter = eh_reg->u.eh_try.first_catch;
- iter;
- iter = iter->next_catch)
- if (iter->label == dst_snode_label)
- return iter;
-
- return nullptr;
-}
-
-std::unique_ptr<eh_dispatch_cfg_superedge>
-eh_dispatch_cfg_superedge::make (supernode *src_snode,
- supernode *dst_snode,
- ::edge e,
- const geh_dispatch *eh_dispatch_stmt)
-{
- const eh_status *eh = src_snode->get_function ()->eh;
- gcc_assert (eh);
- int region_idx = gimple_eh_dispatch_region (eh_dispatch_stmt);
- gcc_assert (region_idx > 0);
- gcc_assert ((*eh->region_array)[region_idx]);
- eh_region eh_reg = (*eh->region_array)[region_idx];
- gcc_assert (eh_reg);
- switch (eh_reg->type)
- {
- default:
- gcc_unreachable ();
- case ERT_CLEANUP:
- // TODO
- gcc_unreachable ();
- break;
- case ERT_TRY:
- {
- eh_catch ehc = get_catch (eh_reg, dst_snode);
- return std::make_unique<eh_dispatch_try_cfg_superedge>
- (src_snode, dst_snode,
- e, eh_dispatch_stmt,
- eh_reg, ehc);
- }
- break;
- case ERT_ALLOWED_EXCEPTIONS:
- return std::make_unique<eh_dispatch_allowed_cfg_superedge>
- (src_snode, dst_snode,
- e, eh_dispatch_stmt,
- eh_reg);
- break;
- case ERT_MUST_NOT_THROW:
- // TODO
- gcc_unreachable ();
- break;
- }
-}
-
-eh_dispatch_cfg_superedge::
-eh_dispatch_cfg_superedge (supernode *src,
- supernode *dst,
- ::edge e,
- const geh_dispatch *eh_dispatch_stmt,
- eh_region eh_reg)
-: cfg_superedge (src, dst, e),
- m_eh_dispatch_stmt (eh_dispatch_stmt),
- m_eh_region (eh_reg)
-{
- gcc_assert (m_eh_region);
-}
-
-const eh_status &
-eh_dispatch_cfg_superedge::get_eh_status () const
-{
- const eh_status *eh = m_src->get_function ()->eh;
- gcc_assert (eh);
- return *eh;
-}
-
-// class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
-
-/* Implementation of superedge::dump_label_to_pp for CFG superedges for
- "eh_dispatch" statements for ERT_TRY regions. */
-
-void
-eh_dispatch_try_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const
-{
- if (!user_facing)
- pp_string (pp, "ERT_TRY: ");
- if (m_eh_catch)
- {
- bool first = true;
- for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter))
- {
- if (!first)
- pp_string (pp, ", ");
- pp_printf (pp, "on catch %qT", TREE_VALUE (iter));
- first = false;
- }
- }
- else
- pp_string (pp, "on uncaught exception");
-}
-
-bool
-eh_dispatch_try_cfg_superedge::
-apply_constraints (region_model *model,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out) const
-{
- return model->apply_constraints_for_eh_dispatch_try
- (*this, ctxt, exception_type, out);
-}
-
-// class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
-
-eh_dispatch_allowed_cfg_superedge::
-eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
- const geh_dispatch *eh_dispatch_stmt,
- eh_region eh_reg)
-: eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg)
-{
- gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS);
-
- /* We expect two sibling out-edges at an eh_dispatch from such a region:
-
- - one to a bb without a gimple label, with a resx,
- for exceptions of expected types
-
- - one to a bb with a gimple label, with a call to __cxa_unexpected,
- for exceptions of unexpected types.
-
- Set m_kind for this edge accordingly. */
- gcc_assert (e->src->succs->length () == 2);
- tree label_for_unexpected_exceptions = eh_reg->u.allowed.label;
- tree label_for_dest_enode = dst->get_label ();
- if (label_for_dest_enode == label_for_unexpected_exceptions)
- m_kind = eh_kind::unexpected;
- else
- {
- gcc_assert (label_for_dest_enode == nullptr);
- m_kind = eh_kind::expected;
- }
-}
-
-void
-eh_dispatch_allowed_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const
-{
- if (!user_facing)
- {
- switch (m_kind)
- {
- default:
- gcc_unreachable ();
- case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
- pp_string (pp, "expected: ");
- break;
- case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
- pp_string (pp, "unexpected: ");
- break;
- }
- pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: ");
- eh_region eh_reg = get_eh_region ();
- bool first = true;
- for (tree iter = eh_reg->u.allowed.type_list; iter;
- iter = TREE_CHAIN (iter))
- {
- if (!first)
- pp_string (pp, ", ");
- pp_printf (pp, "%qT", TREE_VALUE (iter));
- first = false;
- }
- }
-}
-
-bool
-eh_dispatch_allowed_cfg_superedge::
-apply_constraints (region_model *model,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out) const
-{
- return model->apply_constraints_for_eh_dispatch_allowed
- (*this, ctxt, exception_type, out);
-}
-
-/* Implementation of superedge::dump_label_to_pp for interprocedural
- superedges. */
-
-void
-callgraph_superedge::dump_label_to_pp (pretty_printer *pp,
- bool user_facing ATTRIBUTE_UNUSED) const
-{
- switch (m_kind)
- {
- default:
- case SUPEREDGE_CFG_EDGE:
- gcc_unreachable ();
-
- case SUPEREDGE_CALL:
- pp_printf (pp, "call");
- break;
-
- case SUPEREDGE_RETURN:
- pp_printf (pp, "return");
- break;
-
- case SUPEREDGE_INTRAPROCEDURAL_CALL:
- pp_printf (pp, "intraproc link");
- break;
- }
-}
-
-/* Get the function that was called at this interprocedural call/return
- edge. */
-
-function *
-callgraph_superedge::get_callee_function () const
-{
- return get_ultimate_function_for_cgraph_edge (m_cedge);
-}
-
-/* Get the calling function at this interprocedural call/return edge. */
-
-function *
-callgraph_superedge::get_caller_function () const
-{
- return m_cedge->caller->get_fun ();
-}
-
-/* Get the fndecl that was called at this interprocedural call/return
- edge. */
-
-tree
-callgraph_superedge::get_callee_decl () const
-{
- return get_callee_function ()->decl;
-}
-
-/* Get the gcall * of this interprocedural call/return edge. */
-
-const gcall &
-callgraph_superedge::get_call_stmt () const
-{
- if (m_cedge)
- return *m_cedge->call_stmt;
-
- return *m_src->get_final_call ();
-}
-
-/* Get the calling fndecl at this interprocedural call/return edge. */
-
-tree
-callgraph_superedge::get_caller_decl () const
-{
- return get_caller_function ()->decl;
-}
-
-/* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it
- to *OUT if OUT is non-NULL), and return the corresponding argument
- at the callsite. */
-
-tree
-callgraph_superedge::get_arg_for_parm (tree parm_to_find,
- callsite_expr *out) const
-{
- gcc_assert (TREE_CODE (parm_to_find) == PARM_DECL);
-
- tree callee = get_callee_decl ();
- const gcall &call_stmt = get_call_stmt ();
-
- unsigned i = 0;
- for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
- iter_parm = DECL_CHAIN (iter_parm), ++i)
- {
- if (i >= gimple_call_num_args (&call_stmt))
- return NULL_TREE;
- if (iter_parm == parm_to_find)
- {
- if (out)
- *out = callsite_expr::from_zero_based_param (i);
- return gimple_call_arg (&call_stmt, i);
- }
- }
-
- /* Not found. */
- return NULL_TREE;
-}
-
-/* Look for a use of ARG_TO_FIND as an argument at this callsite.
- If found, return the default SSA def of the corresponding parm within
- the callee, and if OUT is non-NULL, write the index to *OUT.
- Only the first match is handled. */
-
-tree
-callgraph_superedge::get_parm_for_arg (tree arg_to_find,
- callsite_expr *out) const
-{
- tree callee = get_callee_decl ();
- const gcall &call_stmt = get_call_stmt ();
-
- unsigned i = 0;
- for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
- iter_parm = DECL_CHAIN (iter_parm), ++i)
- {
- if (i >= gimple_call_num_args (&call_stmt))
- return NULL_TREE;
- tree param = gimple_call_arg (&call_stmt, i);
- if (arg_to_find == param)
- {
- if (out)
- *out = callsite_expr::from_zero_based_param (i);
- return ssa_default_def (get_callee_function (), iter_parm);
- }
- }
-
- /* Not found. */
- return NULL_TREE;
-}
-
-/* Map caller_expr back to an expr within the callee, or return NULL_TREE.
- If non-NULL is returned, populate OUT. */
-
-tree
-callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr,
- callsite_expr *out) const
-{
- /* Is it an argument (actual param)? If so, convert to
- parameter (formal param). */
- tree parm = get_parm_for_arg (caller_expr, out);
- if (parm)
- return parm;
- /* Otherwise try return value. */
- if (caller_expr == gimple_call_lhs (&get_call_stmt ()))
- {
- if (out)
- *out = callsite_expr::from_return_value ();
- return DECL_RESULT (get_callee_decl ());
- }
-
- return NULL_TREE;
-}
-
-/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
- If non-NULL is returned, populate OUT. */
-
-tree
-callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr,
- callsite_expr *out) const
-{
- if (callee_expr == NULL_TREE)
- return NULL_TREE;
-
- /* If it's a parameter (formal param), get the argument (actual param). */
- if (TREE_CODE (callee_expr) == PARM_DECL)
- return get_arg_for_parm (callee_expr, out);
-
- /* Similar for the default SSA name of the PARM_DECL. */
- if (TREE_CODE (callee_expr) == SSA_NAME
- && SSA_NAME_IS_DEFAULT_DEF (callee_expr)
- && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
- return get_arg_for_parm (SSA_NAME_VAR (callee_expr), out);
-
- /* Otherwise try return value. */
- if (callee_expr == DECL_RESULT (get_callee_decl ()))
- {
- if (out)
- *out = callsite_expr::from_return_value ();
- return gimple_call_lhs (&get_call_stmt ());
- }
-
- return NULL_TREE;
+ if (!m_op)
+ return true;
+ return m_op->supports_bulk_merge_p ();
}
} // namespace ana
#include "ordered-hash-map.h"
#include "cfg.h"
#include "basic-block.h"
+#include "cfgloop.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "digraph.h"
#include "except.h"
+#include "analyzer/ops.h"
+
using namespace ana;
namespace ana {
class supergraph;
class supernode;
class superedge;
- class callgraph_superedge;
- class call_superedge;
- class return_superedge;
- class cfg_superedge;
- class switch_cfg_superedge;
- class eh_dispatch_cfg_superedge;
- class eh_dispatch_try_cfg_superedge;
- class eh_dispatch_allowed_cfg_superedge;
class supercluster;
class dot_annotator;
class logger;
-/* An enum for discriminating between superedge subclasses. */
-
-enum edge_kind
-{
- SUPEREDGE_CFG_EDGE,
- SUPEREDGE_CALL,
- SUPEREDGE_RETURN,
- SUPEREDGE_INTRAPROCEDURAL_CALL
-};
-
/* Flags for controlling the appearance of .dot dumps. */
enum supergraph_dot_flags
struct dump_args_t
{
dump_args_t (enum supergraph_dot_flags flags,
- const dot_annotator *node_annotator)
+ const dot_annotator *node_annotator,
+ const exploded_graph *eg)
: m_flags (flags),
- m_node_annotator (node_annotator)
+ m_node_annotator (node_annotator),
+ m_eg (eg)
{}
enum supergraph_dot_flags m_flags;
const dot_annotator *m_node_annotator;
+ const exploded_graph *m_eg;
};
typedef supercluster cluster_t;
};
auto_vec<std::pair<gimple *, unsigned> > m_old_stmt_uids;
};
-/* A "supergraph" is a directed graph formed by joining together all CFGs,
- linking them via interprocedural call and return edges.
+/* A directed graph class representing the users's code,
+ with nodes representing locations within functions, and
+ edges representing transitions between them.
- Basic blocks are split at callsites, so that a call statement occurs
- twice: once at the end of a supernode, and a second instance at the
- start of the next supernode (to handle the return). */
+ For historical reasons we call this the "supergraph", although
+ this is now a misnomer as we no longer add callgraph edges to this
+ graph: the edges within the supergraph are purely intraprocedural:
+ either linking consecutive stmts in a basic block, or linking
+ basic blocks (corresponding to CFG edges). However, all functions
+ are within the same graph. */
class supergraph : public digraph<supergraph_traits>
{
public:
- supergraph (logger *logger);
+ supergraph (region_model_manager &mgr, logger *logger);
~supergraph ();
supernode *get_node_for_function_entry (const function &fun) const
{
- return get_node_for_block (ENTRY_BLOCK_PTR_FOR_FN (&fun));
+ return get_initial_node_for_block (ENTRY_BLOCK_PTR_FOR_FN (&fun));
}
supernode *get_node_for_function_exit (const function &fun) const
{
- return get_node_for_block (EXIT_BLOCK_PTR_FOR_FN (&fun));
+ return get_final_node_for_block (EXIT_BLOCK_PTR_FOR_FN (&fun));
}
- supernode *get_node_for_block (basic_block bb) const
+ supernode *get_initial_node_for_block (basic_block bb) const
{
return *const_cast <bb_to_node_t &> (m_bb_to_initial_node).get (bb);
}
- /* Get the supernode containing the second half of the gcall &
- at an interprocedural call, within the caller. */
- supernode *get_caller_next_node (cgraph_edge *edge) const
- {
- return (*const_cast <cgraph_edge_to_node_t &>
- (m_cgraph_edge_to_caller_next_node).get (edge));
- }
-
- call_superedge *get_edge_for_call (cgraph_edge *edge) const
+ supernode *get_final_node_for_block (basic_block bb) const
{
- return (*const_cast <cgraph_edge_to_call_superedge_t &>
- (m_cgraph_edge_to_call_superedge).get (edge));
+ return *const_cast <bb_to_node_t &> (m_bb_to_final_node).get (bb);
}
- return_superedge *get_edge_for_return (cgraph_edge *edge) const
- {
- return (*const_cast <cgraph_edge_to_return_superedge_t &>
- (m_cgraph_edge_to_return_superedge).get (edge));
- }
-
- superedge *get_intraprocedural_edge_for_call (cgraph_edge *edge) const
+ supernode *get_supernode_for_stmt (const gimple *stmt) const
{
- return (*const_cast <cgraph_edge_to_intraproc_superedge_t &>
- (m_cgraph_edge_to_intraproc_superedge).get (edge));
+ auto iter = m_node_for_stmt.find (stmt);
+ gcc_assert (iter != m_node_for_stmt.end ());
+ return iter->second;
}
- cfg_superedge *get_edge_for_cfg_edge (edge e) const
+ superedge *get_superedge_for_phis (::edge cfg_edge) const
{
- return (*const_cast <cfg_edge_to_cfg_superedge_t &>
- (m_cfg_edge_to_cfg_superedge).get (e));
- }
-
- supernode *get_supernode_for_stmt (const gimple *stmt) const
- {
- return (*const_cast <stmt_to_node_t &>(m_stmt_to_node_t).get
- (const_cast <gimple *> (stmt)));
+ auto iter = m_edges_for_phis.find (cfg_edge);
+ if (iter != m_edges_for_phis.end ())
+ return iter->second;
+ return nullptr;
}
void dump_dot_to_pp (pretty_printer *pp, const dump_args_t &) const;
int num_nodes () const { return m_nodes.length (); }
int num_edges () const { return m_edges.length (); }
- supernode *get_node_by_index (int idx) const
- {
- return m_nodes[idx];
- }
-
unsigned get_num_snodes (const function *fun) const
{
function_to_num_snodes_t &map
return *map.get (fun);
}
-private:
- supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
- gimple_seq phi_nodes);
- cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e);
- call_superedge *add_call_superedge (supernode *src, supernode *dest,
- cgraph_edge *cedge);
- return_superedge *add_return_superedge (supernode *src, supernode *dest,
- cgraph_edge *cedge);
+ void log_stats (logger *logger) const;
- /* Data. */
+ void delete_nodes (const std::set<supernode *> &snodes);
- typedef ordered_hash_map<basic_block, supernode *> bb_to_node_t;
- bb_to_node_t m_bb_to_initial_node;
- bb_to_node_t m_bb_to_final_node;
+ /* Implemented in supergraph-fixup-locations.cc. */
+ void fixup_locations (logger *);
- typedef ordered_hash_map<cgraph_edge *, supernode *> cgraph_edge_to_node_t;
- cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
- cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
+ /* Implemented in supergraph-simplify.cc. */
+ void simplify (logger *);
- typedef ordered_hash_map< ::edge, cfg_superedge *>
- cfg_edge_to_cfg_superedge_t;
- cfg_edge_to_cfg_superedge_t m_cfg_edge_to_cfg_superedge;
+ /* Implemented in supergraph-sorting.cc. */
+ void sort_nodes (logger *logger);
- typedef ordered_hash_map<cgraph_edge *, call_superedge *>
- cgraph_edge_to_call_superedge_t;
- cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
+ supernode *add_node (function *fun, basic_block bb, logger *logger);
- typedef ordered_hash_map<cgraph_edge *, return_superedge *>
- cgraph_edge_to_return_superedge_t;
- cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
+private:
+ gimple *
+ populate_for_basic_block (basic_block bb,
+ function *fun,
+ logger *logger);
+
+ void
+ add_sedges_for_cfg_edge (supernode *src,
+ supernode *dest,
+ ::edge e,
+ gimple *control_stmt,
+ region_model_manager &mgr,
+ logger *logger);
+
+ void dump_dot_to_gv_for_loop (graphviz_out &gv, const dump_args_t &,
+ class loop *, function *) const;
+ void dump_dot_to_gv_for_bb (graphviz_out &gv, const dump_args_t &,
+ basic_block, function *) const;
+
+ /* Implemented in supergraph-sorting.cc. */
+ void
+ reorder_nodes_and_ids (const std::vector<supernode *> &ordering,
+ logger *logger);
- typedef ordered_hash_map<cgraph_edge *, superedge *>
- cgraph_edge_to_intraproc_superedge_t;
- cgraph_edge_to_intraproc_superedge_t m_cgraph_edge_to_intraproc_superedge;
+ /* Data. */
- typedef ordered_hash_map<gimple *, supernode *> stmt_to_node_t;
- stmt_to_node_t m_stmt_to_node_t;
+ typedef ordered_hash_map<basic_block, supernode *> bb_to_node_t;
+ bb_to_node_t m_bb_to_initial_node;
+ bb_to_node_t m_bb_to_final_node;
+
+ std::map<const gimple *, supernode *> m_node_for_stmt;
+ std::map<::edge, superedge *> m_edges_for_phis;
typedef hash_map<const function *, unsigned> function_to_num_snodes_t;
function_to_num_snodes_t m_function_to_num_snodes;
saved_uids m_stmt_uids;
+
+ /* Hand out unique IDs to supernodes, to make it easier
+ to track them when deleting/splitting etc (they're easier to
+ think about when debugging than pointer values). */
+ int m_next_snode_id;
+ std::vector<supernode *> m_snode_by_id;
};
/* A node within a supergraph. */
class supernode : public dnode<supergraph_traits>
{
public:
- supernode (function *fun, basic_block bb, gcall *returning_call,
- gimple_seq phi_nodes, int index)
- : m_fun (fun), m_bb (bb), m_returning_call (returning_call),
- m_phi_nodes (phi_nodes), m_index (index)
+ supernode (function *fun, basic_block bb, int id)
+ : m_fun (fun), m_bb (bb),
+ m_loc (UNKNOWN_LOCATION),
+ m_stmt_loc (UNKNOWN_LOCATION),
+ m_id (id),
+ m_original_id (id),
+ m_label (NULL_TREE),
+ m_preserve_p (false),
+ m_state_merger_node (false)
{}
function *get_function () const { return m_fun; }
{
return m_bb == ENTRY_BLOCK_PTR_FOR_FN (m_fun);
}
-
- bool return_p () const
+ bool exit_p () const
{
return m_bb == EXIT_BLOCK_PTR_FOR_FN (m_fun);
}
void dump_dot (graphviz_out *gv, const dump_args_t &args) const override;
void dump_dot_id (pretty_printer *pp) const;
- std::unique_ptr<json::object> to_json () const;
-
- location_t get_start_location () const;
- location_t get_end_location () const;
-
- /* Returns iterator at the start of the list of phi nodes, if any. */
- gphi_iterator start_phis ()
- {
- gimple_seq *pseq = &m_phi_nodes;
-
- /* Adapted from gsi_start_1. */
- gphi_iterator i;
-
- i.ptr = gimple_seq_first (*pseq);
- i.seq = pseq;
- i.bb = i.ptr ? gimple_bb (i.ptr) : nullptr;
-
- return i;
- }
-
- gcall *get_returning_call () const
+ void print (pretty_printer *pp) const
{
- return m_returning_call;
+ pp_printf (pp, "SN %i", m_id);
}
- gimple *get_last_stmt () const
- {
- if (m_stmts.length () == 0)
- return nullptr;
- return m_stmts[m_stmts.length () - 1];
- }
+ std::unique_ptr<json::object> to_json () const;
- gcall *get_final_call () const
- {
- gimple *stmt = get_last_stmt ();
- if (stmt == nullptr)
- return nullptr;
- return dyn_cast<gcall *> (stmt);
- }
+ location_t get_location () const { return m_loc; }
- unsigned int get_stmt_index (const gimple *stmt) const;
+ tree get_label () const { return m_label; }
- tree get_label () const;
+ bool preserve_p () const { return m_preserve_p; }
- function * const m_fun; // alternatively could be stored as runs of indices within the supergraph
+ function * const m_fun;
const basic_block m_bb;
- gcall * const m_returning_call; // for handling the result of a returned call
- gimple_seq m_phi_nodes; // ptr to that of the underlying BB, for the first supernode for the BB
- auto_vec<gimple *> m_stmts;
- const int m_index; /* unique within the supergraph as a whole. */
+ location_t m_loc;
+ location_t m_stmt_loc; // for debugging
+ int m_id; /* unique within the supergraph as a whole. */
+ const int m_original_id;
+ tree m_label;
+ bool m_preserve_p;
+ bool m_state_merger_node;
};
-/* An abstract base class encapsulating an edge within a supergraph.
- Edges can be CFG edges, or calls/returns for callgraph edges. */
+/* An edge within the supergraph, with an optional operation.
+ Edges can be CFG edges or edges between statements, or persist
+ in order to give more opportunities for state-merging when
+ building the exploded graph. */
class superedge : public dedge<supergraph_traits>
{
public:
+ superedge (supernode *src, supernode *dest,
+ std::unique_ptr<operation> op,
+ ::edge cfg_edge)
+ : dedge<supergraph_traits> (src, dest),
+ m_op (std::move (op)),
+ m_cfg_edge (cfg_edge)
+ {
+ /* All edges are intraprocedural. */
+ gcc_assert (m_src->get_function ()
+ == m_dest->get_function ());
+ }
+
virtual ~superedge () {}
void dump (pretty_printer *pp) const;
void dump_dot (graphviz_out *gv, const dump_args_t &args)
const final override;
- virtual void dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const = 0;
+ const operation *get_op () const { return m_op.get (); }
+ void set_op (std::unique_ptr<operation> op) { m_op = std::move (op); }
+
+ void dump_label_to_pp (pretty_printer *pp,
+ bool user_facing) const;
std::unique_ptr<json::object> to_json () const;
- enum edge_kind get_kind () const { return m_kind; }
+ const supernode *get_dest_snode () const { return m_dest; }
- virtual cfg_superedge *dyn_cast_cfg_superedge () { return nullptr; }
- virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return nullptr; }
- virtual const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const { return nullptr; }
- virtual const eh_dispatch_cfg_superedge *dyn_cast_eh_dispatch_cfg_superedge () const { return nullptr; }
- virtual const eh_dispatch_try_cfg_superedge *dyn_cast_eh_dispatch_try_cfg_superedge () const { return nullptr; }
- virtual const eh_dispatch_allowed_cfg_superedge *dyn_cast_eh_dispatch_allowed_cfg_superedge () const { return nullptr; }
- virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return nullptr; }
- virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return nullptr; }
- virtual call_superedge *dyn_cast_call_superedge () { return nullptr; }
- virtual const call_superedge *dyn_cast_call_superedge () const { return nullptr; }
- virtual return_superedge *dyn_cast_return_superedge () { return nullptr; }
- virtual const return_superedge *dyn_cast_return_superedge () const { return nullptr; }
+ ::edge get_any_cfg_edge () const { return m_cfg_edge; }
- ::edge get_any_cfg_edge () const;
- cgraph_edge *get_any_callgraph_edge () const;
+ bool preserve_p () const;
label_text get_description (bool user_facing) const;
- protected:
- superedge (supernode *src, supernode *dest, enum edge_kind kind)
- : dedge<supergraph_traits> (src, dest),
- m_kind (kind)
- {}
+ bool
+ supports_bulk_merge_p () const;
- public:
- const enum edge_kind m_kind;
+private:
+ std::unique_ptr<operation> m_op;
+ ::edge m_cfg_edge;
};
/* An ID representing an expression at a callsite:
int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown". */
};
-/* A subclass of superedge with an associated callgraph edge (either a
- call or a return). */
-
-class callgraph_superedge : public superedge
-{
- public:
- callgraph_superedge (supernode *src, supernode *dst, enum edge_kind kind,
- cgraph_edge *cedge)
- : superedge (src, dst, kind),
- m_cedge (cedge)
- {}
-
- void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
- final override;
-
- callgraph_superedge *dyn_cast_callgraph_superedge () final override
- {
- return this;
- }
- const callgraph_superedge *dyn_cast_callgraph_superedge () const
- final override
- {
- return this;
- }
-
- function *get_callee_function () const;
- function *get_caller_function () const;
- tree get_callee_decl () const;
- tree get_caller_decl () const;
- const gcall &get_call_stmt () const;
- tree get_arg_for_parm (tree parm, callsite_expr *out) const;
- tree get_parm_for_arg (tree arg, callsite_expr *out) const;
- tree map_expr_from_caller_to_callee (tree caller_expr,
- callsite_expr *out) const;
- tree map_expr_from_callee_to_caller (tree callee_expr,
- callsite_expr *out) const;
-
- cgraph_edge *const m_cedge;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const callgraph_superedge *>::test (const superedge *sedge)
-{
- return (sedge->get_kind () == SUPEREDGE_INTRAPROCEDURAL_CALL
- || sedge->get_kind () == SUPEREDGE_CALL
- || sedge->get_kind () == SUPEREDGE_RETURN);
-}
-
-namespace ana {
-
-/* A subclass of superedge representing an interprocedural call. */
-
-class call_superedge : public callgraph_superedge
-{
- public:
- call_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
- : callgraph_superedge (src, dst, SUPEREDGE_CALL, cedge)
- {}
-
- call_superedge *dyn_cast_call_superedge () final override
- {
- return this;
- }
- const call_superedge *dyn_cast_call_superedge () const final override
- {
- return this;
- }
-
- return_superedge *get_edge_for_return (const supergraph &sg) const
- {
- return sg.get_edge_for_return (m_cedge);
- }
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const call_superedge *>::test (const superedge *sedge)
-{
- return sedge->get_kind () == SUPEREDGE_CALL;
-}
-
-namespace ana {
-
-/* A subclass of superedge represesnting an interprocedural return. */
-
-class return_superedge : public callgraph_superedge
-{
- public:
- return_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
- : callgraph_superedge (src, dst, SUPEREDGE_RETURN, cedge)
- {}
-
- return_superedge *dyn_cast_return_superedge () final override { return this; }
- const return_superedge *dyn_cast_return_superedge () const final override
- {
- return this;
- }
-
- call_superedge *get_edge_for_call (const supergraph &sg) const
- {
- return sg.get_edge_for_call (m_cedge);
- }
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const return_superedge *>::test (const superedge *sedge)
-{
- return sedge->get_kind () == SUPEREDGE_RETURN;
-}
-
-namespace ana {
-
-/* A subclass of superedge that corresponds to a CFG edge. */
-
-class cfg_superedge : public superedge
-{
- public:
- cfg_superedge (supernode *src, supernode *dst, ::edge e)
- : superedge (src, dst, SUPEREDGE_CFG_EDGE),
- m_cfg_edge (e)
- {}
-
- void dump_label_to_pp (pretty_printer *pp, bool user_facing) const override;
- cfg_superedge *dyn_cast_cfg_superedge () final override { return this; }
- const cfg_superedge *dyn_cast_cfg_superedge () const final override { return this; }
-
- ::edge get_cfg_edge () const { return m_cfg_edge; }
- int get_flags () const { return m_cfg_edge->flags; }
- int true_value_p () const { return get_flags () & EDGE_TRUE_VALUE; }
- int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; }
- int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
-
- size_t get_phi_arg_idx () const;
- tree get_phi_arg (const gphi *phi) const;
-
- location_t get_goto_locus () const { return m_cfg_edge->goto_locus; }
-
- private:
- const ::edge m_cfg_edge;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const cfg_superedge *>::test (const superedge *sedge)
-{
- return sedge->get_kind () == SUPEREDGE_CFG_EDGE;
-}
-
-namespace ana {
-
-/* A subclass for edges from switch statements, retaining enough
- information to identify the pertinent cases, and for adding labels
- when rendering via graphviz. */
-
-class switch_cfg_superedge : public cfg_superedge {
- public:
- switch_cfg_superedge (supernode *src, supernode *dst, ::edge e);
-
- const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const
- final override
- {
- return this;
- }
-
- void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
- final override;
-
- gswitch *get_switch_stmt () const
- {
- return as_a <gswitch *> (m_src->get_last_stmt ());
- }
-
- const vec<tree> &get_case_labels () const { return m_case_labels; }
-
- bool implicitly_created_default_p () const;
-
-private:
- auto_vec<tree> m_case_labels;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const switch_cfg_superedge *>::test (const superedge *sedge)
-{
- return sedge->dyn_cast_switch_cfg_superedge () != nullptr;
-}
-
-namespace ana {
-
-/* A subclass for edges from eh_dispatch statements, retaining enough
- information to identify the various types being caught, vs the
- "unhandled type" case, and for adding labels when rendering
- via graphviz.
- This is abstract; there are concrete subclasses based on the type
- of the eh_region. */
-
-class eh_dispatch_cfg_superedge : public cfg_superedge
-{
- public:
- static std::unique_ptr<eh_dispatch_cfg_superedge>
- make (supernode *src,
- supernode *dest,
- ::edge e,
- const geh_dispatch *eh_dispatch_stmt);
-
- const eh_dispatch_cfg_superedge *dyn_cast_eh_dispatch_cfg_superedge () const
- final override
- {
- return this;
- }
-
- const geh_dispatch *
- get_eh_dispatch_stmt () const
- {
- return m_eh_dispatch_stmt;
- }
-
- const eh_status &get_eh_status () const;
- eh_region get_eh_region () const { return m_eh_region; }
-
- virtual bool
- apply_constraints (region_model *model,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out) const = 0;
-
-protected:
- eh_dispatch_cfg_superedge (supernode *src, supernode *dst, ::edge e,
- const geh_dispatch *eh_dispatch_stmt,
- eh_region eh_reg);
-
-private:
- const geh_dispatch *m_eh_dispatch_stmt;
- eh_region m_eh_region;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const eh_dispatch_cfg_superedge *>::test (const superedge *sedge)
-{
- return sedge->dyn_cast_eh_dispatch_cfg_superedge () != nullptr;
-}
-
-namespace ana {
-
-/* A concrete subclass for edges from an eh_dispatch statements
- for ERT_TRY regions. */
-
-class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
-{
- public:
- eh_dispatch_try_cfg_superedge (supernode *src, supernode *dst, ::edge e,
- const geh_dispatch *eh_dispatch_stmt,
- eh_region eh_reg,
- eh_catch ehc)
- : eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg),
- m_eh_catch (ehc)
- {
- gcc_assert (eh_reg->type == ERT_TRY);
- }
-
- const eh_dispatch_try_cfg_superedge *
- dyn_cast_eh_dispatch_try_cfg_superedge () const final override
- {
- return this;
- }
-
- void dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const final override;
-
- eh_catch get_eh_catch () const { return m_eh_catch; }
-
- bool
- apply_constraints (region_model *model,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out)
- const final override;
-
-private:
- eh_catch m_eh_catch;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const eh_dispatch_try_cfg_superedge *>::test (const superedge *sedge)
-{
- return sedge->dyn_cast_eh_dispatch_try_cfg_superedge () != nullptr;
-}
-
-namespace ana {
-
-/* A concrete subclass for edges from an eh_dispatch statements
- for ERT_ALLOWED_EXCEPTIONS regions. */
-
-class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
-{
- public:
- enum eh_kind
- {
- expected,
- unexpected
- };
-
- eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
- const geh_dispatch *eh_dispatch_stmt,
- eh_region eh_reg);
-
- const eh_dispatch_allowed_cfg_superedge *
- dyn_cast_eh_dispatch_allowed_cfg_superedge () const final override
- {
- return this;
- }
-
- void dump_label_to_pp (pretty_printer *pp,
- bool user_facing) const final override;
-
- bool
- apply_constraints (region_model *model,
- region_model_context *ctxt,
- tree exception_type,
- std::unique_ptr<rejected_constraint> *out)
- const final override;
-
- enum eh_kind get_eh_kind () const { return m_kind; }
-
-private:
- enum eh_kind m_kind;
-};
-
-} // namespace ana
-
-template <>
-template <>
-inline bool
-is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::test (const superedge *sedge)
-{
- return sedge->dyn_cast_eh_dispatch_allowed_cfg_superedge () != nullptr;
-}
-
-namespace ana {
/* Base class for adding additional content to the .dot output
for a supergraph. */
class dot_annotator
{
public:
- virtual ~dot_annotator () {}
- virtual bool add_node_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
- const supernode &n ATTRIBUTE_UNUSED,
- bool within_table ATTRIBUTE_UNUSED)
- const
+ virtual ~dot_annotator () = default;
+
+ virtual void
+ add_node_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
+ const supernode &n ATTRIBUTE_UNUSED) const
{
- return false;
+ // no-op
}
- virtual void add_stmt_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
- const gimple *stmt ATTRIBUTE_UNUSED,
- bool within_row ATTRIBUTE_UNUSED)
- const {}
- virtual bool add_after_node_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
- const supernode &n ATTRIBUTE_UNUSED)
- const
+
+ virtual void
+ add_extra_objects (graphviz_out *gv ATTRIBUTE_UNUSED) const
{
- return false;
+ // no-op
}
};
-extern cgraph_edge *supergraph_call_edge (function *fun, const gimple *stmt);
-extern function *get_ultimate_function_for_cgraph_edge (cgraph_edge *edge);
-
} // namespace ana
#endif /* GCC_ANALYZER_SUPERGRAPH_H */
if (!merger->mergeable_svalue_p (other))
return nullptr;
+ /* Reject attempts to merge pointers that point to different base regions,
+ except for the case where both are string literals. */
+ if (auto this_region = maybe_get_region ())
+ if (auto other_region = other->maybe_get_region ())
+ if (this_region != other_region
+ && (this_region->get_kind () != RK_STRING
+ || other_region->get_kind () != RK_STRING))
+ return nullptr;
+
/* Widening. */
/* Merge: (new_cst, existing_cst) -> widen (existing, new). */
if (maybe_get_constant () && other->maybe_get_constant ())
- {
- return mgr->get_or_create_widening_svalue (other->get_type (),
- merger->get_function_point (),
- other, this);
- }
+ return mgr->get_or_create_widening_svalue (other->get_type (),
+ merger->get_supernode (),
+ other, this);
/* Merger of:
this: BINOP (X, OP, CST)
&& binop_sval->get_arg1 ()->get_kind () == SK_CONSTANT
&& other->get_kind () != SK_WIDENING)
return mgr->get_or_create_widening_svalue (other->get_type (),
- merger->get_function_point (),
+ merger->get_supernode (),
other, this);
/* Merge: (Widen(existing_val, V), existing_val) -> Widen (existing_val, V)
{
const widening_svalue *widening_sval1 = (const widening_svalue *)sval1;
const widening_svalue *widening_sval2 = (const widening_svalue *)sval2;
- if (int point_cmp = function_point::cmp (widening_sval1->get_point (),
- widening_sval2->get_point ()))
- return point_cmp;
+ if (int index_cmp = (widening_sval1->get_snode ()->m_id
+ - widening_sval2->get_snode ()->m_id))
+ return index_cmp;
if (int base_cmp = svalue::cmp_ptr (widening_sval1->get_base_svalue (),
widening_sval2->get_base_svalue ()))
return base_cmp;
{
pp_string (pp, "WIDENING(");
pp_character (pp, '{');
- m_point.print (pp, format (false));
+ m_snode->print (pp);
pp_string (pp, "}, ");
m_base_sval->dump_to_pp (pp, simple);
pp_string (pp, ", ");
pp_string (pp, "widening_svalue (");
pp_string (pp, ", ");
pp_character (pp, '{');
- m_point.print (pp, format (false));
+ m_snode->print (pp);
pp_string (pp, "}, ");
m_base_sval->dump_to_pp (pp, simple);
pp_string (pp, ", ");
widening_svalue::print_dump_widget_label (pretty_printer *pp) const
{
pp_printf (pp, "widening_svalue at ");
- m_point.print (pp, format (false));
+ m_snode->print (pp);
}
/* Implementation of svalue::add_dump_widget_children vfunc for
struct setjmp_record
{
setjmp_record (const exploded_node *enode,
+ const superedge *sedge,
const gcall &setjmp_call)
- : m_enode (enode), m_setjmp_call (&setjmp_call)
+ : m_enode (enode), m_sedge (sedge), m_setjmp_call (&setjmp_call)
{
}
bool operator== (const setjmp_record &other) const
{
return (m_enode == other.m_enode
+ && m_sedge == other.m_sedge
&& m_setjmp_call == other.m_setjmp_call);
}
void add_to_hash (inchash::hash *hstate) const
{
hstate->add_ptr (m_enode);
+ hstate->add_ptr (m_sedge);
hstate->add_ptr (m_setjmp_call);
}
static int cmp (const setjmp_record &rec1, const setjmp_record &rec2);
const exploded_node *m_enode;
+ const superedge *m_sedge;
const gcall *m_setjmp_call;
// non-null, but we can't use a reference since we're putting these in a hash_map
};
/* A support class for uniquifying instances of widening_svalue. */
struct key_t
{
- key_t (tree type, const function_point &point,
+ key_t (tree type, const supernode *snode,
const svalue *base_sval, const svalue *iter_sval)
- : m_type (type), m_point (point),
+ : m_type (type), m_snode (snode),
m_base_sval (base_sval), m_iter_sval (iter_sval)
{}
bool operator== (const key_t &other) const
{
return (m_type == other.m_type
- && m_point == other.m_point
+ && m_snode == other.m_snode
&& m_base_sval == other.m_base_sval
&& m_iter_sval == other.m_iter_sval);
}
bool is_empty () const { return m_type == reinterpret_cast<tree> (2); }
tree m_type;
- function_point m_point;
+ const supernode *m_snode;
const svalue *m_base_sval;
const svalue *m_iter_sval;
};
DIR_UNKNOWN
};
- widening_svalue (symbol::id_t id, tree type, const function_point &point,
+ widening_svalue (symbol::id_t id, tree type, const supernode *snode,
const svalue *base_sval, const svalue *iter_sval)
: svalue (complexity::from_pair (base_sval->get_complexity (),
iter_sval->get_complexity ()),
id,
type),
- m_point (point),
+ m_snode (snode),
m_base_sval (base_sval), m_iter_sval (iter_sval)
{
gcc_assert (base_sval->can_have_associated_state_p ());
void accept (visitor *v) const final override;
- const function_point &get_point () const { return m_point; }
+ const supernode *get_snode () const { return m_snode; }
const svalue *get_base_svalue () const { return m_base_sval; }
const svalue *get_iter_svalue () const { return m_iter_sval; }
tree rhs_cst) const;
private:
- function_point m_point;
+ const supernode *m_snode;
const svalue *m_base_sval;
const svalue *m_iter_sval;
};
bool inherited_state_p () const final override { return false; }
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
bool can_purge_p (state_t s) const final override
state_t m_ended;
private:
- void on_va_start (sm_context &sm_ctxt, const supernode *node,
- const gcall &call) const;
- void on_va_copy (sm_context &sm_ctxt, const supernode *node,
- const gcall &call) const;
- void on_va_arg (sm_context &sm_ctxt, const supernode *node,
- const gcall &call) const;
- void on_va_end (sm_context &sm_ctxt, const supernode *node,
- const gcall &call) const;
+ void on_va_start (sm_context &sm_ctxt, const gcall &call) const;
+ void on_va_copy (sm_context &sm_ctxt, const gcall &call) const;
+ void on_va_arg (sm_context &sm_ctxt, const gcall &call) const;
+ void on_va_end (sm_context &sm_ctxt, const gcall &call) const;
void check_for_ended_va_list (sm_context &sm_ctxt,
- const supernode *node,
- const gcall &call,
const svalue *arg,
const char *usage_fnname) const;
};
bool
va_list_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
if (gimple_call_internal_p (call_stmt)
&& gimple_call_internal_fn (call_stmt) == IFN_VA_ARG)
{
- on_va_arg (sm_ctxt, node, call);
+ on_va_arg (sm_ctxt, call);
return false;
}
break;
case BUILT_IN_VA_START:
- on_va_start (sm_ctxt, node, call);
+ on_va_start (sm_ctxt, call);
break;
case BUILT_IN_VA_COPY:
- on_va_copy (sm_ctxt, node, call);
+ on_va_copy (sm_ctxt, call);
break;
case BUILT_IN_VA_END:
- on_va_end (sm_ctxt, node, call);
+ on_va_end (sm_ctxt, call);
break;
}
}
void
va_list_state_machine::on_va_start (sm_context &sm_ctxt,
- const supernode *,
const gcall &call) const
{
const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
if (arg)
{
/* Transition from start state to "started". */
- if (sm_ctxt.get_state (&call, arg) == m_start)
- sm_ctxt.set_next_state (&call, arg, m_started);
+ if (sm_ctxt.get_state (arg) == m_start)
+ sm_ctxt.set_next_state (arg, m_started);
}
}
void
va_list_state_machine::check_for_ended_va_list (sm_context &sm_ctxt,
- const supernode *node,
- const gcall &call,
const svalue *arg,
const char *usage_fnname) const
{
- if (sm_ctxt.get_state (&call, arg) == m_ended)
- sm_ctxt.warn (node, &call, arg,
+ if (sm_ctxt.get_state (arg) == m_ended)
+ sm_ctxt.warn (arg,
std::make_unique<va_list_use_after_va_end>
(*this, arg, NULL_TREE, usage_fnname));
}
void
va_list_state_machine::on_va_copy (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call) const
{
const svalue *src_arg = get_stateful_va_copy_arg (sm_ctxt, call, 1);
if (src_arg)
- check_for_ended_va_list (sm_ctxt, node, call, src_arg, "va_copy");
+ check_for_ended_va_list (sm_ctxt, src_arg, "va_copy");
const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, 0);
if (dst_arg)
{
/* Transition from start state to "started". */
- if (sm_ctxt.get_state (&call, dst_arg) == m_start)
- sm_ctxt.set_next_state (&call, dst_arg, m_started);
+ if (sm_ctxt.get_state (dst_arg) == m_start)
+ sm_ctxt.set_next_state (dst_arg, m_started);
}
}
void
va_list_state_machine::on_va_arg (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call) const
{
const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
if (arg)
- check_for_ended_va_list (sm_ctxt, node, call, arg, "va_arg");
+ check_for_ended_va_list (sm_ctxt, arg, "va_arg");
}
/* Update state machine for a "va_end" call. */
void
va_list_state_machine::on_va_end (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call) const
{
const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
if (arg)
{
- state_t s = sm_ctxt.get_state (&call, arg);
+ state_t s = sm_ctxt.get_state (arg);
/* Transition from "started" to "ended". */
if (s == m_started)
- sm_ctxt.set_next_state (&call, arg, m_ended);
+ sm_ctxt.set_next_state (arg, m_ended);
else if (s == m_ended)
- check_for_ended_va_list (sm_ctxt, node, call, arg, "va_end");
+ check_for_ended_va_list (sm_ctxt, arg, "va_end");
}
}
/* Override of pending_diagnostic::add_call_event,
adding a custom call_event subclass. */
void add_call_event (const exploded_edge &eedge,
- checker_path *emission_path) override
+ const gcall &call_stmt,
+ checker_path &emission_path) override
{
/* As per call_event, but show the number of variadic arguments
in the call. */
if (dst_node->get_state ().m_region_model->get_current_frame ()
== frame_reg)
{
- const exploded_node *src_node = eedge.m_src;
- const program_point &src_point = src_node->get_point ();
- const int src_stack_depth = src_point.get_stack_depth ();
- const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
- const gcall &call_stmt = *as_a <const gcall *> (last_stmt);
int num_variadic_arguments
= get_num_variadic_arguments (dst_node->get_function ()->decl,
call_stmt);
- emission_path->add_event
+ emission_path.add_event
(std::make_unique<va_arg_call_event>
(eedge,
- event_loc_info (last_stmt ? last_stmt->location : UNKNOWN_LOCATION,
- src_point.get_fndecl (),
- src_stack_depth),
+ event_loc_info (eedge.m_src),
num_variadic_arguments));
}
else
- pending_diagnostic::add_call_event (eedge, emission_path);
+ pending_diagnostic::add_call_event (eedge, call_stmt, emission_path);
}
protected:
virtual ~dnode () {}
virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
+ void add_in_edge (edge_t *e)
+ {
+ m_preds.safe_push (e);
+ }
+ void remove_in_edge (edge_t *e)
+ {
+ m_preds.unordered_remove (find_edge_idx (m_preds, e));
+ }
+ void add_out_edge (edge_t *e)
+ {
+ m_succs.safe_push (e);
+ }
+ void remove_out_edge (edge_t *e)
+ {
+ m_succs.unordered_remove (find_edge_idx (m_succs, e));
+ }
+
+public:
auto_vec<edge_t *> m_preds;
auto_vec<edge_t *> m_succs;
+
+private:
+ static unsigned
+ find_edge_idx (auto_vec<edge_t *> &edges, edge_t *e)
+ {
+ for (unsigned i = 0; i < edges.length (); ++i)
+ if (edges[i] == e)
+ return i;
+ gcc_unreachable ();
+ }
};
/* Abstract base class for an edge in a directed graph. */
{
public:
typedef typename GraphTraits::node_t node_t;
+ typedef typename GraphTraits::edge_t edge_t;
typedef typename GraphTraits::dump_args_t dump_args_t;
dedge (node_t *src, node_t *dest)
virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
- node_t *const m_src;
- node_t *const m_dest;
+ void set_dest (node_t *new_dest)
+ {
+ node_t *old_dest = m_dest;
+ if (new_dest != old_dest)
+ {
+ old_dest->remove_in_edge (static_cast<edge_t *> (this));
+ m_dest = new_dest;
+ new_dest->add_in_edge (static_cast<edge_t *> (this));
+ }
+ }
+
+ node_t *m_src;
+ node_t *m_dest;
};
/* Abstract base class for a directed graph.
void add_node (node_t *node);
void add_edge (edge_t *edge);
+ virtual void
+ add_any_extra_stmts (graphviz_out &) const
+ {
+ // no-op hook
+ }
+
auto_delete_vec<node_t> m_nodes;
auto_delete_vec<edge_t> m_edges;
};
FOR_EACH_VEC_ELT (m_edges, i, e)
e->dump_dot (&gv, args);
+ add_any_extra_stmts (gv);
+
/* Terminate "digraph" */
gv.outdent ();
pp_string (pp, "}");
to bail out after analyzing the first basic block.
@end quotation
-First, we build a @code{supergraph} which combines the callgraph and all
-of the CFGs into a single directed graph, with both interprocedural and
-intraprocedural edges. The nodes and edges in the supergraph are called
-``supernodes'' and ``superedges'', and often referred to in code as
-@code{snodes} and @code{sedges}. Basic blocks in the CFGs are split at
-interprocedural calls, so there can be more than one supernode per
-basic block. Most statements will be in just one supernode, but a call
-statement can appear in two supernodes: at the end of one for the call,
-and again at the start of another for the return.
+First, we build a directed graph to represent the user's code.
+For historical reasons we call this the @code{supergraph}, although
+this is now a misnomer as we no longer add callgraph edges to this graph.
+The nodes and edges in the supergraph are called ``supernodes'' and
+``superedges'', and often referred to in code as @code{snodes} and
+@code{sedges}.
+
+We make a node in the supergraph before every gimple statement, with
+edges representing the transitions between statements within a basic block,
+along with additional nodes and edges at CFG edges.
+
+The nodes in the supergraph represent locations in the user's code,
+and discrete points between operations. The edges represent transitions
+between these locations. Each edge in the supergraph can have an optional
+@code{operation} associated with it, representing a single state transition
+that occurs along the edge, such as
-The supergraph can be seen using @option{-fdump-analyzer-supergraph}.
+@itemize @bullet
+
+@item
+individual non-control-flow gimple statements (such as an assignment)
+
+@item
+control flow statements on a CFG edge that impose a condition for the
+transition to be possible (e.g. a branch of a conditional or a
+@code{switch} case)
+
+@item
+the collection of phi nodes at the entry to a basic block, with an
+associated CFG edge (so that these all take effect simultaneously)
+
+@item
+etc
+@end itemize
+
+There can be multiple nodes and edges in the supergraph corresponding to
+a single CFG edge so that e.g. we can handle filtering states on a condition
+separately from handling the effect of the phi nodes if the condition
+was satisfied.
+
+The analyzer in GCC 10 - GCC 15 attempted to have a single supernode per
+basic block for the sake of efficiency, but given that state transitions
+can happen mid-block, this became unmaintainable, hence we now have
+fine-grained nodes with one node/edge per gimple statement.
+
+Having built the supergraph from the CFGs of all of the functions in
+the user's code, we manipulate it:
+
+@itemize @bullet
+@item
+We fixup locations to try to ensure that every supernode has a reasonable
+@code{location_t} value referring to the location in the user's source.
+This is necessary, since in the gimple IR seen by the analyzer, many gimple
+statements have no location associated with them.
+
+@item
+We simplify the supergraph to remove redundant nodes and edges, such as
+those that are simply no-ops that add no useful location information.
+This can eliminate about 5-10% of the nodes.
+
+@item
+We sort and renumber the nodes into an order that we hope will lead to
+efficient state merging when exploring the graph (see below).
+@end itemize
+
+The supergraph can be seen at each stage using
+@option{-fdump-analyzer-supergraph}, which creates a series of
+@file{SRC.supergraph.N.KIND.dot} GraphViz files files showing the state
+of the supergraph after each of the above.
We then build an @code{analysis_plan} which walks the callgraph to
determine which calls might be suitable for being summarized (rather
we're not using the algorithm described in that paper, just the
``exploded graph'' terminology.
-We reuse nodes for <point, state> pairs we've already seen, and avoid
+We reuse nodes for <point,@w{ }state> pairs we've already seen, and avoid
tracking state too closely, so that (hopefully) we rapidly converge
on a final exploded graph, and terminate the analysis. We also bail
-out if the number of exploded <end-of-basic-block, state> nodes gets
-larger than a particular multiple of the total number of basic blocks
+out if the number of exploded <point,@w{ }state> nodes gets
+larger than a particular multiple of the total number of supernodes,
(to ensure termination in the face of pathological state-explosion
cases, or bugs). We also stop exploring a point once we hit a limit
of states for that point.
then we can detect a double-free of "ptr". We can then emit a path
to reach the problem by finding the simplest route through the graph.
-Program points in the analysis are much more fine-grained than in the
-CFG and supergraph, with points (and thus potentially exploded nodes)
-for various events, including before individual statements.
-By default the exploded graph merges multiple consecutive statements
-in a supernode into one exploded edge to minimize the size of the
-exploded graph. This can be suppressed via
-@option{-fanalyzer-fine-grained}.
-The fine-grained approach seems to make things simpler and more debuggable
-that other approaches I tried, in that each point is responsible for one
-thing.
-
-Program points in the analysis also have a "call string" identifying the
+Program points in the analysis are a combination of a supernode
+together with a "call string" identifying the
stack of callsites below them, so that paths in the exploded graph
correspond to interprocedurally valid paths: we always return to the
correct call site, propagating state information accordingly.
exploded nodes in the form @samp{EN: @var{index}} (e.g. @samp{SN: 2} and
@samp{EN:29}).
-The supergraph can be seen using @option{-fdump-analyzer-supergraph-graph}.
+The supergraph can be seen using @option{-fdump-analyzer-supergraph}.
The exploded graph can be seen using @option{-fdump-analyzer-exploded-graph}
and other dump options. Exploded nodes are color-coded in the .dot output
OTHER_GCC_ARGS \
-wrapper gdb,--args \
-fdump-analyzer-stderr \
- -fanalyzer-fine-grained \
-fdump-ipa-analyzer=stderr
@end smallexample
gives valuable context into what's happening when stepping through the
analyzer
-@item @code{-fanalyzer-fine-grained}
-which splits the effect of every statement into its own
-exploded_node, rather than the default (which tries to combine
-successive stmts to reduce the size of the exploded_graph). This makes
-it easier to see exactly where a particular change happens.
-
@item @code{-fdump-ipa-analyzer=stderr}
which dumps the GIMPLE IR seen by the analyzer pass to stderr
Other useful options:
@itemize @bullet
-@item @code{-fdump-analyzer-exploded-graph}
-which dumps a @file{SRC.eg.dot} GraphViz file that I can look at (with
+@item @code{-fdump-analyzer-supergraph}
+which dumps @file{SRC.supergraph.N.KIND.dot} GraphViz files that I can look at (with
python-xdot)
+@item @code{-fdump-analyzer-exploded-graph}
+which dumps a @file{SRC.eg.dot} GraphViz file
+
@item @code{-fdump-analyzer-exploded-nodes-2}
which dumps a @file{SRC.eg.txt} file containing the full @code{exploded_graph}.
@subsection Other Debugging Techniques
To compare two different exploded graphs, try
-@code{-fdump-analyzer-exploded-nodes-2 -fdump-noaddr -fanalyzer-fine-grained}.
+@code{-fdump-analyzer-exploded-nodes-2 -fdump-noaddr}.
This will dump a @file{SRC.eg.txt} file containing the full
@code{exploded_graph}. I use @code{diff -u50 -p} to compare two different
such files (e.g. before and after a patch) to find the first place where the
-fanalyzer-call-summaries
-fanalyzer-checker=@var{name}
-fno-analyzer-feasibility
--fanalyzer-fine-grained
-fanalyzer-show-events-in-system-headers
-fno-analyzer-state-merge
-fno-analyzer-state-purge
@opindex fanalyzer-fine-grained
@opindex fno-analyzer-fine-grained
@item -fanalyzer-fine-grained
-This option is intended for analyzer developers.
-
-Internally the analyzer builds an ``exploded graph'' that combines
-control flow graphs with data flow information.
-
-By default, an edge in this graph can contain the effects of a run
-of multiple statements within a basic block. With
-@option{-fanalyzer-fine-grained}, each statement gets its own edge.
+Does nothing. Preserved for backward compatibility.
@opindex fanalyzer-show-duplicate-count
@opindex fno-analyzer-show-duplicate-count
With @option{-fanalyzer-show-events-in-system-headers} such
events are no longer suppressed.
+@opindex fanalyzer-simplify-supergraph
+@opindex fno-analyzer-simplify-supergraph
+@item -fno-analyzer-simplify-supergraph
+This option is intended for analyzer developers.
+
+By default, the analyzer performs various simplifications to the
+program supergraph before analyzing it. With
+@option{-fno-analyzer-simplify-supergraph} this simplification can be
+suppressed, for debugging issues with it.
+
@opindex fanalyzer-state-merge
@opindex fno-analyzer-state-merge
@item -fno-analyzer-state-merge
@opindex fdump-analyzer-supergraph
@item -fdump-analyzer-supergraph
Dump representations of the ``supergraph'' suitable for viewing with
-GraphViz to @file{@var{file}.supergraph.dot} and to
-@file{@var{file}.supergraph-eg.dot}. These show all of the
-control flow graphs in the program, with interprocedural edges for
-calls and returns. The second dump contains annotations showing nodes
-in the ``exploded graph'' and diagnostics associated with them.
+GraphViz to @file{@var{file}.supergraph.@var{index}.@var{kind}.dot}.
+These show all of the control flow graphs in the program, at various stages
+of the analysis. The precise set of dumps and what they show is subject
+to change.
@opindex fdump-analyzer-untracked
@item -fdump-analyzer-untracked
def to_string (self):
result = '<ana::supernode 0x%x' % intptr(self.gdbval)
if intptr(self.gdbval):
- result += ' (SN %i)' % intptr(self.gdbval['m_index'])
+ result += ' (SN %i)' % intptr(self.gdbval['m_id'])
result += '>'
return result
#if defined (INCLUDE_ALGORITHM) || !defined (HAVE_SWAP_IN_UTILITY)
# include <algorithm>
#endif
+#ifdef INCLUDE_DEQUE
+# include <deque>
+#endif
#ifdef INCLUDE_LIST
# include <list>
#endif
/* { dg-begin-multiline-output "" }
int32_t *ptr = (int32_t *) __builtin_malloc (n * 2);
^~~~~~~~~~~~~~~~~~~~~~~~
- 'test_symbolic': event 1
+ 'test_symbolic': events 1-2
int32_t *ptr = (int32_t *) __builtin_malloc (n * 2);
^~~~~~~~~~~~~~~~~~~~~~~~
|
- (1) allocated 'n * 2' bytes and assigned to 'int32_t *'
+ (1) allocated 'n * 2' bytes here
+ (2) assigned to 'int32_t *'
{ dg-end-multiline-output "" { target c } } */
/* { dg-begin-multiline-output "" }
int32_t *ptr = (int32_t *) __builtin_malloc (n * 2);
~~~~~~~~~~~~~~~~~^~~~~~~
- 'void test_symbolic(int)': event 1
+ 'void test_symbolic(int)': events 1-2
int32_t *ptr = (int32_t *) __builtin_malloc (n * 2);
~~~~~~~~~~~~~~~~~^~~~~~~
|
- (1) allocated '(n * 2)' bytes and assigned to 'int32_t*' {aka '{re:long :re?}int*'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4'
+ (1) allocated '(n * 2)' bytes here
+ (2) assigned to 'int32_t*' {aka '{re:long :re?}int*'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4'
{ dg-end-multiline-output "" { target c++ } } */
/* The analyzer ought to be able to successfully merge all of the
above changes that can reach here into a single state. */
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+ /* __analyzer_dump_exploded_nodes (0);*/ /* { dg-warning "1 processed enode" "FIXME" { xfail *-*-* } } */
}
+
+/* FIXME: */
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
/* { dg-require-effective-target int32plus } */
/* { dg-require-effective-target size24plus } */
-
+/* { dg-additional-options "-Wno-analyzer-symbol-too-complex" } */
/* Reduced from coreutils's cksum.c: cksum_slice8 */
typedef long unsigned int size_t;
unsigned char* cp = (unsigned char*)datap;
while (bytes_read--)
- crc = (crc << 8) ^ crctab[0][((crc >> 24) ^ *cp++) & 0xFF];
+ crc = (crc << 8) ^ crctab[0][((crc >> 24) ^ *cp++) & 0xFF]; /* { dg-bogus "use of uninitialized value" } */
if (feof_unlocked(fp))
break;
}
--- /dev/null
+/* Reduced from ICE seen with coreutils-9.1:lib/human.c, which is GPLv3+. */
+
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+/* { dg-additional-options "-Wno-analyzer-symbol-too-complex" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+char *
+group_number (char *number, size_t numberlen, char const *grouping,
+ char const *thousands_sep)
+{
+ char *d;
+ size_t grouplen = (18446744073709551615UL);
+ size_t thousands_seplen = __builtin_strlen (thousands_sep);
+ size_t i = numberlen;
+
+ char buf[100];
+ __builtin_memcpy (buf, number, numberlen);
+ d = number + numberlen;
+
+ for (;;)
+ {
+ unsigned char g = *grouping;
+
+ if (g)
+ {
+ grouplen = g < 0x7f ? g : i;
+ grouping++;
+ }
+
+ if (i < grouplen)
+ grouplen = i;
+
+ d -= grouplen;
+ i -= grouplen;
+ __builtin_memcpy (d, buf + i, grouplen);
+ if (i == 0)
+ return d;
+
+ d -= thousands_seplen;
+ __builtin_memcpy (d, thousands_sep, thousands_seplen);
+ }
+}
for (i = 0; i < n; i++) {
if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {
- for (; i >= 0; i++) { /* { dg-warning "infinite loop" } */
+ for (; i >= 0; i++) { /* { dg-warning "infinite loop" "" { xfail *-*-* } } */
/* This loop is in the wrong direction, so not technically an
infinite loop ("i" will eventually wrap around), but the
analyzer's condition handling treats the overflow as such.
In any case, the code is suspect and warrants a warning. */
free(arr[i]); /* { dg-bogus "double-'free'" } */
}
- free(arr); /* { dg-warning "leak" } */
+ free(arr); /* { dg-warning "leak" "" { xfail *-*-* } } */
return NULL;
}
}
#define g_assert(expr) \
do { \
- if (expr) ; else /* { dg-warning "check of '\\*words' for NULL after already dereferencing it" } */ \
+ if (expr) ; else /* { dg-warning "check of '\\*words' for NULL after already dereferencing it" "FIXME" { xfail *-*-* } } */ \
g_assertion_message_expr (#expr); \
} while (0)
words = g_strsplit(line->str, " ", 0);
g_string_free(line, TRUE);
- if (strcmp(words[0], "IRQ") == 0) { /* { dg-message "pointer '\\*words' is dereferenced here" } */
+ if (strcmp(words[0], "IRQ") == 0) { /* { dg-message "pointer '\\*words' is dereferenced here" "FIXME" { xfail *-*-* } } */
/* [...snip...] */
g_strfreev(words);
goto redo;
}
- g_assert(words[0] != NULL); /* { dg-message "in expansion of macro 'g_assert'" } */
+ g_assert(words[0] != NULL); /* { dg-message "in expansion of macro 'g_assert'" "FIXME" { xfail *-*-* } } */
/* [...snip...] */
return words;
}
+
+/* FIXME:
+ - old implementation was recording the check at the enode for
+ "_5 = *words_12;", which is within the expansion of g_assert
+ for "words[0]".
+ - new implementation places it at the enode for "if (_5 != 0B)" which
+ is within the definition of g_assert, for "if (expr"), and thus
+ rejected. */
return c1;
}
-/* { dg-final { dg-check-dot "dot-output.c.callgraph.dot" } } */
/* { dg-final { dg-check-dot "dot-output.c.eg.dot" } } */
/* { dg-final { dg-check-dot "dot-output.c.state-purge.dot" } } */
-/* { dg-final { dg-check-dot "dot-output.c.supergraph.dot" } } */
-/* { dg-final { dg-check-dot "dot-output.c.supergraph-eg.dot" } } */
+/* { dg-final { dg-check-dot "dot-output.c.supergraph.0.original.dot" } } */
+/* { dg-final { dg-check-dot "dot-output.c.supergraph.1.fixup-locations.dot" } } */
+/* { dg-final { dg-check-dot "dot-output.c.supergraph.2.simplified.dot" } } */
+/* { dg-final { dg-check-dot "dot-output.c.supergraph.3.sorted.dot" } } */
+/* { dg-final { dg-check-dot "dot-output.c.supergraph.4.eg.dot" } } */
void test_leak_checked_socket (int type)
{
int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
- if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ if (fd == -1)
return;
- // TODO: strange location for leak message
-}
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_bind_on_checked_socket (int type, const char *sockname)
{
memset (&addr, 0, sizeof (addr));
addr.sun_family = AF_UNIX;
strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
- bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
-}
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_listen_without_bind (int type)
{
return n;
}
-/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\) \[^\n\r\]*" } */
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ enodes\\) \[^\n\r\]*" } */
else
b->yy_buf_size *= 2;
- b->yy_ch_buf = (char *) /* { dg-warning "leak" } */
+ b->yy_ch_buf = (char *) /* { dg-warning "leak" "" { xfail *-*-* } } */
/* Include room in for 2 EOB chars. */
yyrealloc( (void *) b->yy_ch_buf,
(yy_size_t) (b->yy_buf_size + 2) );
/* { dg-additional-options "-fno-analyzer-call-summaries" } */
-/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+/* { dg-additional-options "-Wno-analyzer-too-complex -Wno-analyzer-symbol-too-complex" } */
/* { dg-additional-options "-D_POSIX_SOURCE" } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
else
b->yy_buf_size *= 2;
- b->yy_ch_buf = (char *) /* { dg-warning "leak of '\\*b.yy_ch_buf'" "" { target c } } */
- /* { dg-warning "leak of '\\*b.yy_buffer_state::yy_ch_buf'" "" { target c++ } .-1 } */
+ b->yy_ch_buf = (char *) /* { dg-warning "leak of '\\*b.yy_ch_buf'" "" { xfail *-*-* } } */
+ /* { dg-warning "leak of '\\*b.yy_buffer_state::yy_ch_buf'" "" { xfail *-*-* } .-1 } */
/* Include room in for 2 EOB chars. */
yyrealloc( (void *) b->yy_ch_buf,
(yy_size_t) (b->yy_buf_size + 2) );
return -1;
}
+// FIXME: need to disable these to avoid bailing out too early with a "too complex" warning
+#if 0
int nowarn_switch (int i, int a[])
{
switch (i)
default: return warn_switch (a[1], a + 5);
}
}
+#endif
NORETURN void fnoreturn (void);
test_switch_2 (1776);
break;
default:
- test_switch_2 (1492); /* { dg-warning "infinite recursion" "" { xfail *-*-* } } */
+ test_switch_2 (1492); /* { dg-warning "infinite recursion" } */
break;
}
}
uint32_t mask;
if (subdirs != 32)
- mask = (1 << subdirs) - 1; /* { dg-message "shift by count \\('33'\\) >= precision of type \\('\[0-9\]+'\\)" "" { xfail c++26 } } */
+ mask = (1 << subdirs) - 1; /* { dg-message "shift by count \\('33'\\) >= precision of type \\('\[0-9\]+'\\)" } */
else
mask = -1;
- return mask ^ ((1U << inactive) - 1); /* { dg-message "shift by negative count \\('-1'\\)" "" { xfail c++26 } } */
+ return mask ^ ((1U << inactive) - 1); /* { dg-message "shift by negative count \\('-1'\\)" } */
}
void f1 (int);
--- /dev/null
+/* Regression test for ICE seen handling callbacks. */
+
+int
+middle (int flag,
+ int (*on_success) (),
+ int (*on_failure) ())
+{
+ if (flag)
+ return on_success ();
+ else
+ return on_failure ();
+}
+
+static int
+success_callback ()
+{
+ return 0;
+}
+
+static int
+failure_callback_x ()
+{
+ return -1;
+}
+
+static int
+failure_callback_y ()
+{
+ return -1;
+}
+
+int
+outer (int flag_a, int flag_b)
+{
+ return middle (flag_a, success_callback,
+ (flag_b
+ ? failure_callback_x
+ : failure_callback_y));
+}
for (i=0; i<256; i++) {
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+ /* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
__analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+ /* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
for (j=0; j<256; j++) {
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
/* TODO(xfail^^^): should report TRUE twice. */
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
for (k=0; k<256; k++) {
__analyzer_eval (k < 256); /* { dg-warning "TRUE" "true" } */
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 processed enodes" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
}
}
}
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
}
- __analyzer_eval (i <= 0); /* { dg-warning "TRUE" "true" } */
-
+ __analyzer_eval (i <= 0); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
+ /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
__analyzer_eval (i == 0); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
/* (should report TRUE twice). */
__analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
- /* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
+ /* { dg-warning "FALSE" "2nd" { target *-*-* } .-1 } */
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
void add_zero_terminator (char *buf)
{
char *end = buf;
- while (end++); /* TODO: arguably we should report this. */
+ while (end++); /* { dg-warning "infinite loop" } */
if (buf < end)
end[-1] = '\0';
}
switch (s->mode) /* { dg-message "if it ever follows 'default:' branch, it will always do so\.\.\." } */
{
case 0:
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enode" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
do_stuff (s, 0);
break;
case 1:
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
do_stuff (s, 17);
break;
case 2:
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
do_stuff (s, 5);
break;
case 3:
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
return 42;
case 4:
- __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
return -3;
}
}
-/* { dg-additional-options "-Wno-analyzer-too-complex" } */
-/* TODO: remove the need for -Wno-analyzer-too-complex. */
-
typedef struct evp_pkey_asn1_method_st EVP_PKEY_ASN1_METHOD;
typedef struct engine_st ENGINE;
struct stack_st_EVP_PKEY_ASN1_METHOD;
if (curbp->b_amark == (AMARK *)NULL)
curbp->b_amark = p;
else
- last->m_next = p; /* { dg-warning "dereference of NULL 'last'" "deref" } */
+ last->m_next = p; /* { dg-warning "dereference of NULL 'last'" "deref" { xfail *-*-* } } */
}
p->m_name = (char)c; /* { dg-bogus "leak of 'p'" "bogus leak" } */
/* { dg-additional-options "-O2 -fno-analyzer-transitivity" } */
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
int *wf;
-/* { dg-additional-options "-O1" } */
+/* { dg-additional-options "-O1 -Wno-analyzer-too-complex" } */
void foo(void *);
struct chanset_t {
Reduced from
https://git.qemu.org/?p=qemu.git;a=blob;f=subprojects/libvhost-user/libvhost-user.c;h=fab7ca17ee1fb27bcfc338527d1aeb9f923aade5;hb=HEAD#l1184
which is licensed under GNU GPLv2 or later. */
-
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint64_t;
*pp = malloc (16); /* { dg-warning "leak" } */
break;
case 1:
- free (*pp);
+ free (*pp); /* { dg-warning "double-free" } */
break;
case 2:
/* no-op. */
{
__builtin_realloc(s, n); /* { dg-warning "ignoring return value of '__builtin_realloc' declared with attribute 'warn_unused_result'" "" { target c } } */
/* { dg-warning "ignoring return value of 'void\\* __builtin_realloc\\(void\\*, (long )*unsigned int\\)' declared with attribute 'warn_unused_result'" "" { target c++ } .-1 } */
-} /* { dg-warning "leak" } */
+} /* { dg-warning "leak" "" { target *-*-* } .-2 } */
+/* { dg-additional-options "-fno-exceptions" } */
typedef unsigned int __u32;
__extension__ typedef __signed__ long long __s64;
__extension__ typedef unsigned long long __u64;
extern int
sprintf(char* dst, const char* fmt, ...)
- __attribute__((__nothrow__));
+ __attribute__((__nothrow__))
+ __attribute__((nonnull (1, 2)));
#include "../../gcc.dg/analyzer/analyzer-decls.h"
char *p = (char *) malloc (sz); /* { dg-message "allocated here" } */
if (!p)
return;
- sprintf (p, "%s/%s", a, b); /* { dg-warning "leak of 'p' " } */
-}
+ sprintf (p, "%s/%s", a, b);
+} /* { dg-warning "leak of 'p' " } */
-/* { dg-additional-options "-Wno-analyzer-too-complex" } */
-
#define NULL ((void *)0)
void test_sentinel (int arg, ...)
typedef __SIZE_TYPE__ size_t;
-extern char *strncpy (char *dst, const char *src, size_t count);
+extern char *
+strncpy (char *dst, const char *src, size_t count)
+ __attribute__ ((nonnull (1, 2)));
char *
test_passthrough (char *dst, const char *src, size_t count)
#include "../../gcc.dg/analyzer/analyzer-decls.h"
-extern char *strstr (const char* str, const char* substr);
+extern char *
+strstr (const char* str, const char* substr)
+ __attribute__ ((nonnull));
char *
test_passthrough (const char* str, const char* substr)
}
# If a testcase doesn't have special options, use these.
-set DEFAULT_CXXFLAGS " -fanalyzer -Wanalyzer-too-complex -fanalyzer-call-summaries"
+set DEFAULT_CXXFLAGS " -fanalyzer -Wanalyzer-too-complex"
# Initialize `dg'.
dg-init
/* { dg-note "\\(1\\) 'a\\.std::.+::_M_ptr' is NULL" "" { target c++14_down } declare_a } */
/* { dg-note "dereference of NULL 'a\\.std::.+::operator->\\(\\)'" "" { target *-*-* } deref_a } */
-/* { dg-note "calling 'std::.+::operator->' from 'main'" "" { target *-*-* } deref_a } */
-/* { dg-note "returning to 'main' from 'std::.+::operator->'" "" { target *-*-* } deref_a } */
/* { dg-note "\\(1\\) 'a\\.std::.+::_M_ptr' is NULL" "" { target c++14_down } declare_a } */
/* { dg-note "dereference of NULL 'a\\.std::.+::operator->\\(\\)'" "" { target *-*-* } deref_a } */
-/* { dg-note "calling 'std::.+::operator->' from 'main'" "" { target *-*-* } deref_a } */
-/* { dg-note "returning to 'main' from 'std::.+::operator->'" "" { target *-*-* } deref_a } */
struct A {int x; int y;};
-int main () { /* { dg-message "\\(1\\) entry to 'main'" "telltale event that we are going within a deeper frame than 'main'" } */
+int main () {
std::shared_ptr<A> a;
a->x = 4; /* { dg-line deref_a } */
/* { dg-warning "dereference of NULL" "" { target *-*-* } deref_a } */
f (B * b, int h, bool)
{
d (b->cls);
- return new j (b, h); // { dg-warning "leak" }
+ return new j (b, h);
}
void
m ()
{
if (i)
- f (&k, 0, false);
+ f (&k, 0, false); // { dg-warning "leak" }
}
virtual void
sx ()
{
- sx ();
+ sx (); /* { dg-warning "infinite recursion" } */
}
};
ALL DOCUMENTS AND THE INFORMATION CONTAINED IN THE CWE ARE PROVIDED ON AN "AS IS" BASIS AND THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE MITRE CORPORATION, ITS BOARD OF TRUSTEES, OFFICERS, AGENTS, AND EMPLOYEES, DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION THEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE MITRE CORPORATION, ITS BOARD OF TRUSTEES, OFFICERS, AGENTS, AND EMPLOYEES BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION OR THE USE OR OTHER DEALINGS IN THE CWE. */
-
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
return x;
}
-long int test_2 (long int x)
+void test_2 (long int x)
{
__analyzer_eval (labs (hide_long (42)) == 42); /* { dg-warning "TRUE" } */
__analyzer_eval (labs (hide_long (-17)) == 17); /* { dg-warning "TRUE" } */
None of these are actually implemented. */
/* Trigger a breakpoint in the analyzer when reached. */
-extern void __analyzer_break (void);
+extern void
+__analyzer_break (void)
+ __attribute__ ((nothrow));
/* Emit a warning describing the 2nd argument (which can be of any
type), at the given verbosity level. This is for use when
debugging, and may be of use in DejaGnu tests. */
-extern void __analyzer_describe (int verbosity, ...);
+extern void
+__analyzer_describe (int verbosity, ...)
+ __attribute__ ((nothrow));
/* Dump copious information about the analyzer’s state when reached. */
-extern void __analyzer_dump (void);
+extern void
+__analyzer_dump (void)
+ __attribute__ ((nothrow));
/* Emit a warning describing the size of the base region of (*ptr). */
-extern void __analyzer_dump_capacity (const void *ptr);
+extern void
+__analyzer_dump_capacity (const void *ptr)
+ __attribute__ ((nothrow));
/* When reached, dump GraphViz .dot source to stderr for a diagram
describing the analyzer’s state. */
-extern void __analyzer_dump_dot (void);
+extern void
+__analyzer_dump_dot (void)
+ __attribute__ ((nothrow));
/* Dump information about what decls have escaped at this point on the path. */
-extern void __analyzer_dump_escaped (void);
+extern void
+__analyzer_dump_escaped (void)
+ __attribute__ ((nothrow));
/* Dump information after analysis on all of the exploded nodes at this
program point.
__analyzer_dump_exploded_nodes (1);
will also dump all of the states within those nodes. */
-extern void __analyzer_dump_exploded_nodes (int);
+extern void
+__analyzer_dump_exploded_nodes (int)
+ __attribute__ ((nothrow));
/* Emit a warning describing what is known about the value of NAME. */
-extern void __analyzer_dump_named_constant (const char *name);
+extern void
+__analyzer_dump_named_constant (const char *name)
+ __attribute__ ((nothrow));
/* Emit a placeholder "note" diagnostic with a path to this call site,
if the analyzer finds a feasible path to it. */
-extern void __analyzer_dump_path (void);
+extern void
+__analyzer_dump_path (void)
+ __attribute__ ((nothrow));
/* Dump the region_model's state to stderr. */
-extern void __analyzer_dump_region_model (void);
+extern void
+__analyzer_dump_region_model (void)
+ __attribute__ ((nothrow));
/* Emit a warning describing the state of the 2nd argument
(which can be of any type) with respect to NAME.
This is for use when debugging, and may be of use in DejaGnu tests. */
-extern void __analyzer_dump_state (const char *name, ...);
+extern void
+__analyzer_dump_state (const char *name, ...)
+ __attribute__ ((nothrow));
/* Dump copious information about the analyzer’s state when reached. */
-extern void __analyzer_dump_xml (void);
+extern void
+__analyzer_dump_xml (void)
+ __attribute__ ((nothrow));
/* Emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the
truthfulness of the argument. */
-extern void __analyzer_eval (int);
+extern void
+__analyzer_eval (int)
+ __attribute__ ((nothrow));
/* Obtain an "unknown" void *. */
-extern void *__analyzer_get_unknown_ptr (void);
+extern void *
+__analyzer_get_unknown_ptr (void)
+ __attribute__ ((nothrow));
/* Complain if PTR doesn't point to a null-terminated string.
TODO: eventually get the strlen of the buffer (without the
optimizer touching it). */
-extern __SIZE_TYPE__ __analyzer_get_strlen (const char *ptr);
+extern __SIZE_TYPE__
+__analyzer_get_strlen (const char *ptr)
+ __attribute__ ((nothrow));
#endif /* #ifndef ANALYZER_DECLS_H. */
}
# If a testcase doesn't have special options, use these.
-set DEFAULT_CFLAGS "-fanalyzer -Wanalyzer-too-complex -Wanalyzer-symbol-too-complex -fanalyzer-call-summaries"
+set DEFAULT_CFLAGS "-fanalyzer -Wanalyzer-too-complex -Wanalyzer-symbol-too-complex"
if { [istarget "*-*-darwin*" ] } {
# On macOS, system headers redefine by default some macros (memcpy,
return result;
}
-boxed_ptr
+void
boxed_free (boxed_ptr ptr)
{
free (ptr.value);
free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
}
-int test_24 (void)
+void test_24 (void)
{
boxed_ptr ptr;
ptr.value = __builtin_alloca (sizeof (int)); /* { dg-message "region created on stack here" } */
free (ptr.value); /* { dg-warning "'free' of 'ptr.value' which points to memory on the stack \\\[CWE-590\\\]" } */
}
-int test_25 (void)
+void test_25 (void)
{
char tmp[100]; /* { dg-message "region created on stack here" } */
boxed_ptr p;
char global_buffer[100]; /* { dg-message "region created here" } */
-int test_26 (void)
+void test_26 (void)
{
boxed_ptr p;
p.value = global_buffer;
return i * j;
}
-int test_consume_two_ints_from_va_list (__builtin_va_list ap1)
+void test_consume_two_ints_from_va_list (__builtin_va_list ap1)
{
int p1, p2;
__builtin_va_list ap2;
__analyzer_dump_path (); /* { dg-message "\\(2\\) \\.\\.\\.to here" } */
}
-int test_7 (void)
+void test_7 (void)
{
if (foo () ? bar () ? baz () : 0 : 0) /* { dg-message "\\(1\\) following 'true' branch\\.\\.\\." } */
__analyzer_dump_path (); /* { dg-message "\\(2\\) \\.\\.\\.to here" } */
aw2.ptrs[1] = malloc (512);
} /* { dg-warning "leak of 'aw2.ptrs.0.'" "leak of element 0" } */
/* { dg-warning "leak of 'aw2.ptrs.1.'" "leak of element 1" { target *-*-* } .-1 } */
+/* { dg-warning "use of uninitialized value '<return-value>'" "missing return" { target *-*-* } .-2 } */
struct union_wrapper uw2;
uw2.u.ptr = malloc (1024);
} /* { dg-warning "leak of 'uw2.u.ptr'" } */
+/* { dg-warning "use of uninitialized value '<return-value>'" "missing return" { target *-*-* } .-1 } */
__analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
}
-int test_1 (int flag_a, int flag_b)
+void test_1 (int flag_a, int flag_b)
{
int i = 17;
long y;
};
-int test_12d (struct coord c)
+void test_12d (struct coord c)
{
struct coord d;
d = c;
return *ptr;
}
-int test_40 (int flag)
+void test_40 (int flag)
{
int i;
if (flag)
return (dx * dx) + (dy * dy) + (dz * dz) <= r_squared;
}
-int test_3 (const struct coord *c1, const struct coord *c2, struct coord *out)
+void test_3 (const struct coord *c1, const struct coord *c2, struct coord *out)
{
out->x = c1->x + c2->x;
out->y = c1->y + c2->y;
{ cb_2 }
};
-int deflate (foo_t *s, int which)
+void deflate (foo_t *s, int which)
{
(*(table[which].func))(s);
}
for (; i >= 0; i--) {
free(arr[i]); /* { dg-bogus "double-'free'" } */
}
- free(arr); /* { dg-bogus "leak" "" { xfail *-*-* } } */
+ free(arr); /* { dg-bogus "leak" } */
return NULL;
}
}
/* { dg-additional-options "-fno-analyzer-state-merge" } */
#include "analyzer-decls.h"
-int test_40 (int flag)
+void test_40 (int flag)
{
int i;
if (flag)
strcpy(basedefault,
"devdata"
"default.cfg");
- return; /* { dg-warning "leak of 'doom2wad'" } */
- /* { dg-warning "leak of 'doomuwad'" "leak" { target *-*-* } .-1 } */
- /* { dg-warning "leak of 'doomwad'" "leak" { target *-*-* } .-2 } */
- /* { dg-warning "leak of 'doom1wad'" "leak" { target *-*-* } .-3 } */
- /* { dg-warning "leak of 'plutoniawad'" "leak" { target *-*-* } .-4 } */
- /* { dg-warning "leak of 'tntwad'" "leak" { target *-*-* } .-5 } */
- /* { dg-warning "leak of 'doom2fwad'" "leak" { target *-*-* } .-6 } */
+ return; /* { dg-warning "leak of 'doom2wad'" "" { xfail *-*-* } } */
+ /* { dg-warning "leak of 'doomuwad'" "leak" { xfail *-*-* } .-1 } */
+ /* { dg-warning "leak of 'doomwad'" "leak" { xfail *-*-* } .-2 } */
+ /* { dg-warning "leak of 'doom1wad'" "leak" { xfail *-*-* } .-3 } */
+ /* { dg-warning "leak of 'plutoniawad'" "leak" { xfail *-*-* } .-4 } */
+ /* { dg-warning "leak of 'tntwad'" "leak" { xfail *-*-* } .-5 } */
+ /* { dg-warning "leak of 'doom2fwad'" "leak" { xfail *-*-* } .-6 } */
}
if (M_CheckParm("-regdev")) {
strcpy(basedefault,
"devdata"
"default.cfg");
- return; /* { dg-warning "leak of 'doom2wad'" } */
- /* { dg-warning "leak of 'doomuwad'" "leak" { target *-*-* } .-1 } */
- /* { dg-warning "leak of 'doomwad'" "leak" { target *-*-* } .-2 } */
- /* { dg-warning "leak of 'doom1wad'" "leak" { target *-*-* } .-3 } */
- /* { dg-warning "leak of 'plutoniawad'" "leak" { target *-*-* } .-4 } */
- /* { dg-warning "leak of 'tntwad'" "leak" { target *-*-* } .-5 } */
- /* { dg-warning "leak of 'doom2fwad'" "leak" { target *-*-* } .-6 } */
+ return; /* { dg-warning "leak of 'doom2wad'" "" { xfail *-*-* } } */
+ /* { dg-warning "leak of 'doomuwad'" "leak" { xfail *-*-* } .-1 } */
+ /* { dg-warning "leak of 'doomwad'" "leak" { xfail *-*-* } .-2 } */
+ /* { dg-warning "leak of 'doom1wad'" "leak" { xfail *-*-* } .-3 } */
+ /* { dg-warning "leak of 'plutoniawad'" "leak" { xfail *-*-* } .-4 } */
+ /* { dg-warning "leak of 'tntwad'" "leak" { xfail *-*-* } .-5 } */
+ /* { dg-warning "leak of 'doom2fwad'" "leak" { xfail *-*-* } .-6 } */
}
if (M_CheckParm("-comdev")) {
strcpy(basedefault,
"devdata"
"default.cfg");
- return; /* { dg-warning "leak of 'doom2wad'" } */
- /* { dg-warning "leak of 'doomuwad'" "leak" { target *-*-* } .-1 } */
- /* { dg-warning "leak of 'doomwad'" "leak" { target *-*-* } .-2 } */
- /* { dg-warning "leak of 'doom1wad'" "leak" { target *-*-* } .-3 } */
- /* { dg-warning "leak of 'plutoniawad'" "leak" { target *-*-* } .-4 } */
- /* { dg-warning "leak of 'tntwad'" "leak" { target *-*-* } .-5 } */
- /* { dg-warning "leak of 'doom2fwad'" "leak" { target *-*-* } .-6 } */
+ return; /* { dg-warning "leak of 'doom2wad'" "" { xfail *-*-* } } */
+ /* { dg-warning "leak of 'doomuwad'" "leak" { xfail *-*-* } .-1 } */
+ /* { dg-warning "leak of 'doomwad'" "leak" { xfail *-*-* } .-2 } */
+ /* { dg-warning "leak of 'doom1wad'" "leak" { xfail *-*-* } .-3 } */
+ /* { dg-warning "leak of 'plutoniawad'" "leak" { xfail *-*-* } .-4 } */
+ /* { dg-warning "leak of 'tntwad'" "leak" { xfail *-*-* } .-5 } */
+ /* { dg-warning "leak of 'doom2fwad'" "leak" { xfail *-*-* } .-6 } */
}
if (!access(doom2fwad, 4)) {
if (cnum == numChannels) {
for (cnum = 0; cnum < numChannels; cnum++)
- if (channels[cnum].sfxinfo->priority >= sfxinfo->priority) /* { dg-warning "dereference of NULL" } */
+ if (channels[cnum].sfxinfo->priority >= sfxinfo->priority) /* { dg-warning "dereference of NULL" "" { xfail *-*-* } } */
break;
if (cnum == numChannels) {
bar ();
if (flag) /* { dg-message "when 'flag == 0'" "branch event" } */
- fclose (fp); /* { dg-bogus "leak" "warning at wrong location" { xfail *-*-* } .-1 } */
-} /* { dg-warning "leak of FILE 'fp'" "warning" { xfail *-*-* } } */
-// TODO(xfail): location of leak message ought to be on closing brace
+ fclose (fp);
+} /* { dg-warning "leak of FILE 'fp'" "warning" } */
void test_2 (const char *path, int flag)
{
__analyzer_eval (st == 0); /* { dg-warning "TRUE" } */
}
-char *test_error_unterminated (int st)
+void test_error_unterminated (int st)
{
char fmt[3] = "abc";
error (st, errno, fmt); /* { dg-warning "stack-based buffer over-read" } */
/* { dg-message "while looking for null terminator for argument 3 \\('&fmt'\\) of 'error'..." "event" { target *-*-* } .-1 } */
}
-char *test_error_at_line_unterminated (int st, int errno)
+void test_error_at_line_unterminated (int st, int errno)
{
char fmt[3] = "abc";
error_at_line (st, errno, __FILE__, __LINE__, fmt); /* { dg-warning "stack-based buffer over-read" } */
/* { dg-message "while looking for null terminator for argument 5 \\('&fmt'\\) of 'error_at_line'..." "event" { target *-*-* } .-1 } */
}
-char *test_error_uninitialized (int st, int errno)
+void test_error_uninitialized (int st, int errno)
{
char fmt[16];
error (st, errno, fmt); /* { dg-warning "use of uninitialized value 'fmt\\\[0\\\]'" } */
/* { dg-message "while looking for null terminator for argument 3 \\('&fmt'\\) of 'error'..." "event" { target *-*-* } .-1 } */
}
-char *test_error_at_line_uninitialized (int st, int errno)
+void test_error_at_line_uninitialized (int st, int errno)
{
char fmt[16];
error_at_line (st, errno, __FILE__, __LINE__, fmt); /* { dg-warning "use of uninitialized value 'fmt\\\[0\\\]'" } */
{
default:
case 0:
- *pp = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
- // TODO: xfail
+ *pp = malloc (16); /* { dg-warning "leak" } */
break;
case 1:
- free (*pp);
+ free (*pp); /* { dg-warning "uninit" } */
+ /* { dg-warning "double-'free'" "" { target *-*-* } .-1 } */
break;
case 2:
/* no-op. */
p0 = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
break;
case 1:
- free (p0); /* { dg-warning "double-'free' of 'p0'" } */
+ free (p0); /* { dg-warning "double-'free' of 'p0'" "" { xfail *-*-* } } */
break;
case 2:
{
default:
case 0:
- *pp = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
- // TODO: xfail
+ *pp = malloc (16); /* { dg-warning "leak" } */
break;
case 1:
- free (*pp);
+ free (*pp); /* { dg-warning "uninit" } */
+ /* { dg-warning "double-'free'" "" { target *-*-* } .-1 } */
break;
case 2:
/* no-op. */
void test_leak_checked_socket (void)
{
int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
- if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ if (fd == -1)
return;
- // TODO: strange location for leak message
-}
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_bind (const char *sockname)
{
memset (&addr, 0, sizeof (addr));
addr.sun_family = AF_UNIX;
strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
- bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
-}
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_listen_on_datagram_socket_without_bind (void)
{
new = accept (sock,
(struct sockaddr *) &clientname,
&size);
- if (new < 0)
+ if (new < 0) /* { dg-bogus "leak" "FIXME" { xfail *-*-* } } */
{
perror ("accept");
exit (EXIT_FAILURE);
void test_leak_checked_socket (void)
{
int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
- if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ if (fd == -1)
return;
- // TODO: strange location for leak message
-}
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_bind_on_checked_socket (const char *sockname)
{
memset (&addr, 0, sizeof (addr));
addr.sun_family = AF_UNIX;
strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
- bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
-}
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+} /* { dg-warning "leak of file descriptor 'fd'" } */
void test_listen_without_bind (void)
{
return ptr;
}
-int test_24 (void)
+void test_24 (void)
{
void *ptr = __builtin_alloca (sizeof (int)); /* { dg-message "region created on stack here" } */
free (ptr); /* { dg-warning "'free' of 'ptr' which points to memory on the stack \\\[CWE-590\\\]" } */
}
-int test_25 (void)
+void test_25 (void)
{
char tmp[100]; /* { dg-message "region created on stack here" } */
void *p = tmp;
char global_buffer[100]; /* { dg-message "region created here" } */
-int test_26 (void)
+void test_26 (void)
{
void *p = global_buffer;
free (p); /* { dg-warning "'free' of 'p' which points to memory not on the heap \\\[CWE-590\\\]" } */
Py_Dealloc((PyObject *)(op)); \
} while (0)
-int test (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
- PyObject *obj_04, PyObject *obj_05, PyObject *obj_06,
- PyObject *obj_07, PyObject *obj_08, PyObject *obj_09,
- PyObject *obj_10, PyObject *obj_11, PyObject *obj_12,
- PyObject *obj_13, PyObject *obj_14, PyObject *obj_15
+void
+test (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
+ PyObject *obj_04, PyObject *obj_05, PyObject *obj_06,
+ PyObject *obj_07, PyObject *obj_08, PyObject *obj_09,
+ PyObject *obj_10, PyObject *obj_11, PyObject *obj_12,
+ PyObject *obj_13, PyObject *obj_14, PyObject *obj_15
)
{
Py_DECREF (obj_01); Py_DECREF (obj_02); Py_DECREF (obj_03);
#include <stdlib.h>
#include "analyzer-decls.h"
-int test (int flag)
+void test (int flag)
{
int other_flag;
if (flag)
*ptr = 42;
}
-int test_2 (int flag)
+void test_2 (int flag)
{
int i;
/* Adapted from drivers/scsi/aacraid/commctrl.c */
-static int aac_send_raw_srb(/* [...snip...] */)
+static void aac_send_raw_srb(/* [...snip...] */)
{
u32 byte_count = 0;
check_uninit (reply.padding[1]); /* { dg-warning "uninitialized value" } */
}
-static int aac_send_raw_srb_fixed(/* [...snip...] */)
+static void aac_send_raw_srb_fixed(/* [...snip...] */)
{
u32 byte_count = 0;
/* { dg-require-effective-target ptr_eq_long } */
-/* { dg-additional-options "-O2 -Wno-shift-count-overflow -Wno-analyzer-symbol-too-complex" } */
+/* { dg-additional-options "-O2 -Wno-shift-count-overflow -Wno-analyzer-symbol-too-complex -fanalyzer-call-summaries" } */
struct lisp;
union vectorlike_header { long size; };
{
long PSEUDOVECTOR_FLAG = 1L << 62;
long PVEC_TYPE_MASK = 0x3fL << 24;
- return ((a->size & (PSEUDOVECTOR_FLAG | PVEC_TYPE_MASK)) /* { dg-bogus "dereference of NULL 'time'" "PR analyzer/107526" { xfail *-*-* } } */
+ return ((a->size & (PSEUDOVECTOR_FLAG | PVEC_TYPE_MASK)) /* { dg-bogus "dereference of NULL 'time'" "PR analyzer/107526" } */
== (PSEUDOVECTOR_FLAG | (code << 24)));
}
return malloc (sizeof (int));
}
-void *
+void
test_alloca (void)
{
void *p = alloca (sizeof (int));
}
int emalloc(int size) { memcheck(__builtin_malloc(size)); } /* { dg-message "allocated here" } */
-int main() { int max_envvar_len = emalloc(max_envvar_len + 1); } /* { dg-message "use of uninitialized value 'max_envvar_len'" } */
+/* { dg-warning "use of uninitialized value '<return-value>'" "missing return" { target *-*-* } .-1 } */
+
+void main() { int max_envvar_len = emalloc(max_envvar_len + 1); } /* { dg-message "use of uninitialized value 'max_envvar_len'" } */
} while (true);
}
-int not_main()
+void not_main()
{
struct list *res;
func(&res);
-/* { dg-additional-options "-O2 -Wno-analyzer-too-complex -Wno-analyzer-symbol-too-complex" } */
+/* { dg-additional-options "-O2 -Wno-analyzer-too-complex -Wno-analyzer-symbol-too-complex -fanalyzer-call-summaries" } */
/* C only: C++ FE optimizes argstr_get_word completely away
and therefore the number of SN diminishes compared to C,
int func6(const int *num)
{
- if (*num) /* { dg-warning "uninitialized" } */
- return *num; /* { dg-warning "uninitialized" } */
+ if (*num) /* { dg-warning "uninitialized" "FIXME: feasibility" { xfail *-*-* } } */
+ return *num; /* { dg-warning "uninitialized" "FIXME: feasibility" { xfail *-*-* } } */
else
return 0;
}
{
int i;
for (i=0; i<max; i++) {
- if (a[i]) /* { dg-warning "uninitialized" } */
+ if (a[i]) /* { dg-warning "uninitialized" "FIXME: feasibility" { xfail *-*-* } } */
printf("func8: %d\n", i);
}
}
struct test t; /* { dg-message "region created on stack here" } */
int num; /* { dg-message "region created on stack here" } */
int arry[10];
- int arry_2[10]; /* { dg-message "region created on stack here" } */
- int go; /* { dg-message "region created on stack here" } */
+ int arry_2[10]; /* { dg-message "region created on stack here" "FIXME: feasibility" { xfail *-*-* } } */
+ int go; /* { dg-message "region created on stack here" "FIXME: feasibility" { xfail *-*-* } } */
int color = BLACK;
func1(&t);
func5(arry, 10);
func6(&num);
- printf("num: %d\n", num); /* { dg-warning "use of uninitialized value 'num'" } */
+ printf("num: %d\n", num); /* { dg-warning "use of uninitialized value 'num'" "FIXME: feasibility" { xfail *-*-* } } */
printf("func7: %d\n", func7());
func8(arry_2, 10);
break;
}
- printf("go :%d\n", go); /* { dg-warning "use of uninitialized value 'go'" } */
+ printf("go :%d\n", go); /* { dg-warning "use of uninitialized value 'go'" "FIXME: feasibility" { xfail *-*-* } } */
return 0;
}
+
+/* FIXME: various tests above failing due to bugs in feasibility logic
+ preventing exit from loop in func_4. */
info = -1;
LAPACKE_xerbla( "LAPACKE_cgbbrd_work", info );
}
- return info; /* { dg-warning "leak of 'q_t'" "leak of q_t" } */
- /* { dg-warning "leak of 'pt_t'" "leak of pt_t" { target *-*-* } .-1 } */
+ return info; /* { dg-warning "leak of 'q_t'" "leak of q_t" { xfail *-*-* } } */
+ /* { dg-warning "leak of 'pt_t'" "leak of pt_t" { xfail *-*-* } .-1 } */
}
/* { dg-require-effective-target int32plus } */
/* TODO (PR analyzer/112528): remove need for this. */
-/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=40 --param analyzer-bb-explosion-factor=10" } */
+/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=200 --param analyzer-bb-explosion-factor=50" } */
/* Minimal replacement of system headers. */
/* { dg-require-effective-target int32plus } */
/* TODO (PR analyzer/112528): remove need for this. */
-/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=40 --param analyzer-bb-explosion-factor=10" } */
+/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=200 --param analyzer-bb-explosion-factor=50" } */
/* Minimal replacement of system headers. */
#define PATH_SEPARATOR ':'
#define LOCALE_ALIAS_PATH "value for LOCALE_ALIAS_PATH"
-const char *
+void
_nl_expand_alias (void)
{
static const char *locale_alias_path;
if (nmap >= maxmap)
if (__builtin_expect (extend_alias_table (), 0))
- return added; /* { dg-warning "leak of FILE 'fp'" } */
+ return added; /* { dg-warning "leak of FILE 'fp'" "" { xfail *-*-* } } */
alias_len = strlen (alias) + 1;
value_len = strlen (value) + 1;
? alias_len + value_len : 1024));
char *new_pool = (char *) realloc (string_space, new_size);
if (new_pool == NULL)
- return added;
+ return added; /* { dg-warning "leak of FILE 'fp'" } */
if (__builtin_expect (string_space != new_pool, 0))
{
string_space_max = new_size;
}
- map[nmap].alias = memcpy (&string_space[string_space_act],
+ map[nmap].alias = memcpy (&string_space[string_space_act], /* { dg-bogus "uninit" "FIXME" { xfail *-*-* } } */
alias, alias_len);
string_space_act += alias_len;
struct a {
int b;
} d() {
-}
+} /* { dg-warning "use of uninitialized value '<return-value>'" } */
void e()
/* { dg-additional-sources pr98599-b.c } */
int b(int x);
-int a() { b(5); }
-int main() { a(); }
+int a() { return b(5); }
+int main() { return a(); }
void test_1 (void)
{
*(char*)malloc (1024) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(1024\\)'" } */
-} /* { dg-warning "leak of 'malloc\\(1024\\)'" "warning" } */
- /* { dg-message "'malloc\\(1024\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(1024\\)'" "warning" { target *-*-* } .-1 } */
+ /* { dg-message "'malloc\\(1024\\)' leaks here" "final event" { target *-*-* } .-2 } */
void test_2 (size_t n)
{
*(char*)malloc (4 * n) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(n \\* 4\\)'" "warning" } */
/* { dg-message "'malloc\\(n \\* 4\\)' could be NULL" "final event" { target *-*-* } .-1 } */
-} /* { dg-warning "leak of 'malloc\\(n \\* 4\\)'" "warning" } */
- /* { dg-message "'malloc\\(n \\* 4\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(n \\* 4\\)'" "warning" { target *-*-* } .-2 } */
+ /* { dg-message "'malloc\\(n \\* 4\\)' leaks here" "final event" { target *-*-* } .-3 } */
/* A compound example. */
{
*(char*)malloc (a + (b * c)) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(a \\+ b \\* c\\)'" "warning" } */
/* { dg-message "'malloc\\(a \\+ b \\* c\\)' could be NULL" "final event" { target *-*-* } .-1 } */
-} /* { dg-warning "leak of 'malloc\\(a \\+ b \\* c\\)'" "warning" } */
- /* { dg-message "'malloc\\(a \\+ b \\* c\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(a \\+ b \\* c\\)'" "warning" { target *-*-* } .-2 } */
+ /* { dg-message "'malloc\\(a \\+ b \\* c\\)' leaks here" "final event" { target *-*-* } .-3 } */
void test_4 (size_t a, size_t b, size_t c)
{
*(char *)malloc (a ? b : c) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(<unknown>\\)'" "warning" } */
/* { dg-message "'malloc\\(<unknown>\\)' could be NULL" "final event" { target *-*-* } .-1 } */
-} /* { dg-warning "leak of 'malloc\\(<unknown>\\)'" "warning" } */
- /* { dg-message "'malloc\\(<unknown>\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(<unknown>\\)'" "warning" { target *-*-* } .-2 } */
+ /* { dg-message "'malloc\\(<unknown>\\)' leaks here" "final event" { target *-*-* } .-3 } */
/* Unary operators. */
void test_5 (size_t a)
{
*(char*)malloc (-a) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(-a\\)'" } */
-} /* { dg-warning "leak of 'malloc\\(-a\\)'" "warning" } */
- /* { dg-message "'malloc\\(-a\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(-a\\)'" "warning" { target *-*-* } .-1 } */
+ /* { dg-message "'malloc\\(-a\\)' leaks here" "final event" { target *-*-* } .-2 } */
void test_6 (size_t a)
{
*(char*)malloc (~a) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(~a\\)'" } */
-} /* { dg-warning "leak of 'malloc\\(~a\\)'" "warning" } */
- /* { dg-message "'malloc\\(~a\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(~a\\)'" "warning" { target *-*-* } .-1 } */
+ /* { dg-message "'malloc\\(~a\\)' leaks here" "final event" { target *-*-* } .-2 } */
/* Field access. */
void test_7a(struct s7 s)
{
*(char*)malloc (s.sz) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(s\\.sz\\)'" } */
-} /* { dg-warning "leak of 'malloc\\(s\\.sz\\)'" "warning" } */
- /* { dg-message "'malloc\\(s\\.sz\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(s\\.sz\\)'" "warning" { target *-*-* } .-1 } */
+ /* { dg-message "'malloc\\(s\\.sz\\)' leaks here" "final event" { target *-*-* } .-2 } */
void test_7b (struct s7 *s)
{
*(char*)malloc (s->sz) = 42; /* { dg-warning "dereference of possibly-NULL 'malloc\\(\\*s\\.sz\\)'" } */
-} /* { dg-warning "leak of 'malloc\\(\\*s\\.sz\\)'" "warning" } */
- /* { dg-message "'malloc\\(\\*s\\.sz\\)' leaks here" "final event" { target *-*-* } .-1 } */
+} /* { dg-warning "leak of 'malloc\\(\\*s\\.sz\\)'" "warning" { target *-*-* } .-1 } */
+ /* { dg-message "'malloc\\(\\*s\\.sz\\)' leaks here" "final event" { target *-*-* } .-2 } */
return;
n1->m_next = malloc (sizeof (struct node));
/* Could free n1->m_next, but not n1. */
- const_unknown_fn (n1); /* { dg-warning "leak of 'n1'" } */
-}
+ const_unknown_fn (n1);
+} /* { dg-warning "leak of 'n1'" } */
void
test_11 (void)
return;
n1->m_next = malloc (sizeof (struct node));
/* Could free n1->m_next, but not n1. */
- unknown_fn (n1->m_next); /* { dg-warning "leak of 'n1'" } */
-}
+ unknown_fn (n1->m_next);
+} /* { dg-warning "leak of 'n1'" } */
void
test_12a (void)
Don't use a prototype, to avoid const mismatches. */
extern char *(getpass) ();
-char test_1 (FILE *logfile)
+void test_1 (FILE *logfile)
{
char *password = getpass (">"); /* { dg-message "\\(1\\) sensitive value acquired here" } */
fprintf (logfile, "got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" "warning" } */
/* { dg-message "\\(2\\) sensitive value 'password' written to output file; acquired at \\(1\\)" "event" { target *-*-* } .-1 } */
}
-char test_2 (FILE *logfile, int i)
+void test_2 (FILE *logfile, int i)
{
char *password = getpass (">"); /* { dg-message "\\(1\\) sensitive value acquired here" } */
fprintf (logfile, "got password[%i]: %s\n", i, password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
/* { dg-message "\\(2\\) sensitive value 'password' written to output file; acquired at \\(1\\)" "event" { target *-*-* } .-1 } */
}
-char test_3 (FILE *logfile)
+void test_3 (FILE *logfile)
{
char *password = getpass (">"); /* { dg-message "\\(1\\) sensitive value acquired here" } */
printf ("got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" "warning" } */
/* { dg-message "\\(2\\) sensitive value 'password' written to output file; acquired at \\(1\\)" "event" { target *-*-* } .-1 } */
}
-char test_4 (FILE *logfile)
+void test_4 (FILE *logfile)
{
char *password = getpass (">"); /* { dg-message "\\(1\\) sensitive value acquired here" } */
fwrite (password, strlen (password), 1, logfile); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" "warning" } */
printf ("%s", value); /* { dg-warning "sensitive value 'value' written to output file \\\[CWE-532\\\]" } */
}
-char test_5 (FILE *logfile)
+void test_5 (FILE *logfile)
{
char *password = getpass (">");
called_by_test_5 (password); /* { dg-message "passing sensitive value 'password' in call to 'called_by_test_5' from 'test_5'" } */
return getpass (">"); /* { dg-message "sensitive value acquired here" } */
}
-char test_6 (FILE *logfile)
+void test_6 (FILE *logfile)
{
char *password = called_by_test_6 (); /* { dg-message "returning sensitive value to 'test_6' from 'called_by_test_6'" } */
printf ("%s", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
assert get_state_node_name(first) == 'first'
assert get_state_node_type(first) == 'struct node *'
- assert len(state['edges']) == 3
+ assert len(state['edges']) == 4
void test_not_enough_args_2 (void)
{
- __analyzer_test_not_enough_args_2_middle (42, "foo");
+ __analyzer_test_not_enough_args_2_middle (42, "foo"); /* { dg-message "calling '__analyzer_test_not_enough_args_2_middle' from 'test_not_enough_args_2' with 1 variadic argument" } */
}
/* Excess args (not a problem). */
#include <string.h>
#include <stdlib.h>
-int test_1 (const char *str, char *ptr)
+void test_1 (const char *str, char *ptr)
{
if (strcmp (str, "VALUE")) /* { dg-message "following 'true' branch \\(when the strings are non-equal\\)\\.\\.\\." } */
free (ptr);
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
}
-int test_2 (const char *str, char *ptr)
+void test_2 (const char *str, char *ptr)
{
if (strcmp (str, "VALUE") == 0) /* { dg-message "following 'true' branch \\(when the strings are equal\\)\\.\\.\\." } */
free (ptr);
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
}
-int test_3 (const char *str, char *ptr)
+void test_3 (const char *str, char *ptr)
{
if (!strcmp (str, "VALUE")) /* { dg-message "following 'true' branch \\(when the strings are equal\\)\\.\\.\\." } */
free (ptr);
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
}
-int test_4 (const char *str, char *ptr)
+void test_4 (const char *str, char *ptr)
{
if (strcmp (str, "VALUE")) /* { dg-message "following 'false' branch \\(when the strings are equal\\)\\.\\.\\." } */
{
extern void external_fn (void *ptr);
-char *test_external_fn (void)
+void test_external_fn (void)
{
char src[10];
char dst[10];
return 1945;
}
__analyzer_dump_path (); /* { dg-message "path" } */
-}
+} /* { dg-warning "use of uninitialized value '<return-value>'" } */
int __attribute__((tainted_args))
test_all_values_covered_implicit_default_2 (enum e x)
return 0;
}
-int test_bitmask_1 (int x)
+void test_bitmask_1 (int x)
{
int flag = 0;
if (x & 0x80)
}
}
-int test_bitmask_2 (int x)
+void test_bitmask_2 (int x)
{
int flag = 0;
if ((x & 0xf80) == 0x80)
int y;
};
-int __attribute__((tainted_args))
+void __attribute__((tainted_args))
test_assert_struct (struct s *p)
{
MY_ASSERT_1 (p->x < p->y); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
return 0;
}
-char test_2(struct foo *f)
+void test_2(struct foo *f)
{
/* not a bug: the data is not known to be tainted: */
*(p + f->offset) = 42;
}
# If a testcase doesn't have special options, use these.
-set DEFAULT_CFLAGS "-fanalyzer -fdiagnostics-path-format=separate-events -Wanalyzer-too-complex -fanalyzer-call-summaries"
+set DEFAULT_CFLAGS "-fanalyzer -fdiagnostics-path-format=separate-events -Wanalyzer-too-complex"
gcc-dg-runtest [lsort [glob $srcdir/$subdir/*.c]] "" $DEFAULT_CFLAGS
return result;
}
-boxed_ptr __attribute__((noinline))
+void __attribute__((noinline))
boxed_free (boxed_ptr ptr)
{
free (ptr.value);
/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
-
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
#include "../analyzer-decls.h"
extern _Bool quit_flag;
/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
-
+/* { dg-skip-if "" { *-*-* } { "-O3" } { "" } } */
#include "../analyzer-decls.h"
void test (int *p)
void bad_realloc(char *s, int n)
{
- char *p = __builtin_realloc(s, n);
-} /* { dg-warning "leak" } */
+ char *p = __builtin_realloc(s, n); } /* { dg-warning "leak" } */
/* { dg-do compile } */
/* { dg-additional-options "-std=gnu17" } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
#include "../../torture/pr57330.c"
+/* { dg-warning "use of uninitialized value" "" { target *-*-* } 11 } */
{
int sum = test_3f_callee (0, x, y, z, 0);
__analyzer_describe (0, sum); /* { dg-message "'UNKNOWN\\(int\\)'" } */
+ /* { dg-message "INIT_VAL\\(x.*\\)\\\+INIT_VAL\\(y.*\\)\\\+INIT_VAL\\(z.*\\)" "" { target *-*-* } .-1 } */
}
return 42;
}
-int test_12 (void (*fnptr) (struct st *))
+void test_12 (void (*fnptr) (struct st *))
{
static struct st s12 = { __FILE__, __LINE__ }; /* { dg-warning "track 's12': yes" } */
fnptr (&s12);
struct link { struct link *next; };
-int free_a_list_badly (struct link *n)
+void free_a_list_badly (struct link *n)
{
while (n) {
free(n); /* { dg-message "freed here" } */
uInt mask;
register uInt *p;
inflate_huft *q;
- struct inflate_huft_s r; /* { dg-message "region created on stack here" } */
+ struct inflate_huft_s r; /* { dg-message "region created on stack here" "" { xfail *-*-* } } */
inflate_huft *u[15];
register int w;
uInt x[15 + 1];
f = 1 << (k - w);
for (j = i >> w; j < z; j += f)
- q[j] = r; /* { dg-warning "use of uninitialized value 'r.base'" } */
+ q[j] = r; /* { dg-warning "use of uninitialized value 'r.base'" "" { xfail *-*-* } } */
mask = (1 << w) - 1;
/* The analyzer thinks that h can be -1 here.
This is probably a false positive. */
- while ((i & mask) != x[h]) { /* { dg-bogus "under-read" "" { xfail *-*-* } } */
+ while ((i & mask) != x[h]) { /* { dg-bogus "under-read" } */
h--;
w -= l;
mask = (1 << w) - 1;
}
};
-/* This is just a copy of leak_stmt_finder for now (subject to change if
- * necssary) */
-
-class refcnt_stmt_finder : public stmt_finder
-{
-public:
- refcnt_stmt_finder (const exploded_graph &eg, tree var)
- : m_eg (eg), m_var (var)
- {
- }
-
- std::unique_ptr<stmt_finder>
- clone () const final override
- {
- return std::make_unique<refcnt_stmt_finder> (m_eg, m_var);
- }
-
- const gimple *
- find_stmt (const exploded_path &epath) final override
- {
- logger *const logger = m_eg.get_logger ();
- LOG_FUNC (logger);
-
- if (m_var && TREE_CODE (m_var) == SSA_NAME)
- {
- /* Locate the final write to this SSA name in the path. */
- const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
-
- int idx_of_def_stmt;
- bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
- if (!found)
- goto not_found;
-
- /* What was the next write to the underlying var
- after the SSA name was set? (if any). */
-
- for (unsigned idx = idx_of_def_stmt + 1; idx < epath.m_edges.length ();
- ++idx)
- {
- const exploded_edge *eedge = epath.m_edges[idx];
- if (logger)
- logger->log ("eedge[%i]: EN %i -> EN %i", idx,
- eedge->m_src->m_index,
- eedge->m_dest->m_index);
- const exploded_node *dst_node = eedge->m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const gimple *stmt = dst_point.get_stmt ();
- if (!stmt)
- continue;
- if (const gassign *assign = dyn_cast<const gassign *> (stmt))
- {
- tree lhs = gimple_assign_lhs (assign);
- if (TREE_CODE (lhs) == SSA_NAME
- && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
- return assign;
- }
- }
- }
-
- not_found:
-
- /* Look backwards for the first statement with a location. */
- int i;
- const exploded_edge *eedge;
- FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
- {
- if (logger)
- logger->log ("eedge[%i]: EN %i -> EN %i", i, eedge->m_src->m_index,
- eedge->m_dest->m_index);
- const exploded_node *dst_node = eedge->m_dest;
- const program_point &dst_point = dst_node->get_point ();
- const gimple *stmt = dst_point.get_stmt ();
- if (stmt)
- if (get_pure_location (stmt->location) != UNKNOWN_LOCATION)
- return stmt;
- }
-
- gcc_unreachable ();
- return NULL;
- }
-
- void update_event_loc_info (event_loc_info &) final override
- {
- /* No-op. */
- }
-
-private:
- const exploded_graph &m_eg;
- tree m_var;
-};
-
class refcnt_mismatch : public pending_diagnostic_subclass<refcnt_mismatch>
{
public:
return;
const auto &eg = ctxt->get_eg ();
- refcnt_stmt_finder finder (*eg, reg_tree);
auto pd = std::make_unique<refcnt_mismatch> (curr_region, ob_refcnt_sval,
actual_refcnt_sval,
reg_tree);
if (pd && eg)
- ctxt->warn (std::move (pd), &finder);
+ ctxt->warn (std::move (pd),
+ make_ploc_fixer_for_epath_for_leak_diagnostic (*eg,
+ NULL_TREE));
}
}
bool inherited_state_p () const final override { return false; }
bool on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const final override;
bool can_purge_p (state_t s) const final override;
void check_for_pyobject_usage_without_gil (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt,
tree op) const;
private:
void check_for_pyobject_in_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
tree callee_fndecl) const;
struct cb_data
{
cb_data (const gil_state_machine &sm, sm_context &sm_ctxt,
- const supernode *snode, const gimple *stmt)
- : m_sm (sm), m_sm_ctxt (sm_ctxt), m_snode (snode), m_stmt (stmt)
+ const gimple *stmt)
+ : m_sm (sm), m_sm_ctxt (sm_ctxt), m_stmt (stmt)
{
}
const gil_state_machine &m_sm;
sm_context &m_sm_ctxt;
- const supernode *m_snode;
const gimple *m_stmt;
};
check_for_pyobject (gimple *, tree op, tree, void *data)
{
cb_data *d = (cb_data *)data;
- d->m_sm.check_for_pyobject_usage_without_gil (d->m_sm_ctxt, d->m_snode,
+ d->m_sm.check_for_pyobject_usage_without_gil (d->m_sm_ctxt,
d->m_stmt, op);
return true;
}
void
gil_state_machine::check_for_pyobject_in_call (sm_context &sm_ctxt,
- const supernode *node,
const gcall &call,
tree callee_fndecl) const
{
tree type = TREE_TYPE (TREE_TYPE (arg));
if (type_based_on_pyobject_p (type))
{
- sm_ctxt.warn (node, &call, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<fncall_without_gil> (*this, call,
callee_fndecl,
i));
bool
gil_state_machine::on_stmt (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt) const
{
const state_t global_state = sm_ctxt.get_global_state ();
"PyEval_SaveThread");
if (global_state == m_released_gil)
{
- sm_ctxt.warn (node, stmt, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<double_save_thread> (*this, call));
sm_ctxt.set_global_state (m_stop);
}
{
/* Find PyObject * args of calls to fns with unknown bodies. */
if (!fndecl_has_gimple_body_p (callee_fndecl))
- check_for_pyobject_in_call (sm_ctxt, node, call, callee_fndecl);
+ check_for_pyobject_in_call (sm_ctxt, call, callee_fndecl);
}
}
else if (global_state == m_released_gil)
- check_for_pyobject_in_call (sm_ctxt, node, call, NULL);
+ check_for_pyobject_in_call (sm_ctxt, call, NULL);
}
else
if (global_state == m_released_gil)
{
/* Walk the stmt, finding uses of PyObject (or "subclasses"). */
- cb_data d (*this, sm_ctxt, node, stmt);
+ cb_data d (*this, sm_ctxt, stmt);
walk_stmt_load_store_addr_ops (const_cast <gimple *> (stmt), &d,
check_for_pyobject,
check_for_pyobject,
void
gil_state_machine::check_for_pyobject_usage_without_gil (sm_context &sm_ctxt,
- const supernode *node,
const gimple *stmt,
tree op) const
{
tree type = TREE_TYPE (op);
if (type_based_on_pyobject_p (type))
{
- sm_ctxt.warn (node, stmt, NULL_TREE,
+ sm_ctxt.warn (NULL_TREE,
std::make_unique<pyobject_usage_without_gil> (*this, op));
sm_ctxt.set_global_state (m_stop);
}
err = -1;
/* [...snip...] */
+
+ return 0;
}
static int sco_sock_getsockopt_fixed(struct socket *sock, int optname, char __user *optval, int __user *optlen)
err = -1;
/* [...snip...] */
+
+ return 0;
}
/* Adapted from sco_sock_getsockopt_old in net/bluetooth/sco.c. */
-int test_1 (char __user *optval, const struct sco_conninfo *in)
+void test_1 (char __user *optval, const struct sco_conninfo *in)
{
struct sco_conninfo cinfo; /* { dg-message "region created on stack here" "where" } */
/* { dg-message "capacity: 6 bytes" "capacity" { target *-*-* } .-1 } */
/* { dg-message "1 byte is uninitialized" "how much note" { target *-*-* } .-1 } */
}
-int test_2 (char __user *optval, const struct sco_conninfo *in)
+void test_2 (char __user *optval, const struct sco_conninfo *in)
{
struct sco_conninfo cinfo;
/* Note: 40 bits of fields, padded to 48. */
/* Adapted from drivers/scsi/aacraid/commctrl.c */
-static int aac_send_raw_srb(/* [...snip...] */
- void __user *user_reply)
+static void aac_send_raw_srb(/* [...snip...] */
+ void __user *user_reply)
{
u32 byte_count = 0;
/* [...snip...] */
}
-static int aac_send_raw_srb_fixed(/* [...snip...] */
- void __user *user_reply)
+static void aac_send_raw_srb_fixed(/* [...snip...] */
+ void __user *user_reply)
{
u32 byte_count = 0;
}
# If a testcase doesn't have special options, use these.
-set DEFAULT_DFLAGS "-fanalyzer -Wanalyzer-too-complex -fanalyzer-call-summaries"
+set DEFAULT_DFLAGS "-fanalyzer -Wanalyzer-too-complex"
# Initialize `dg'.
dg-init
}
# If a testcase doesn't have special options, use these.
-set DEFAULT_FFLAGS "-fanalyzer -Wanalyzer-too-complex -fanalyzer-call-summaries"
+set DEFAULT_FFLAGS "-fanalyzer -Wanalyzer-too-complex"
# Initialize `dg'.
dg-init
! { dg-additional-options "-O0" }
+! { dg-additional-options "-Wno-analyzer-too-complex" }
MODULE M1
IMPLICIT NONE