From 5c4661c4f753304adcdd7d931f0f0a03d3bf45c9 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 19 Sep 2025 13:23:07 -0400 Subject: [PATCH] analyzer: reimplement supergraph to eliminate function_point and stmt_finder 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 ""), 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. * 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. * I don't have performance data yet. 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 to system.h), so I want to apply this patch now in stage 3 (it's a big quality-of-life improvement for 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::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 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::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 (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 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 ::test): Drop. (class call_superedge): Drop. (is_a_helper ::test): Drop. (class return_superedge): Drop. (is_a_helper ::test): Drop. (class cfg_superedge): Drop. (class switch_cfg_superedge): Drop. (is_a_helper ::test): Drop. (class eh_dispatch_cfg_superedge): Drop. (is_a_helper ::test): Drop. (class eh_dispatch_try_cfg_superedge): Drop. (is_a_helper ::test): Drop. (class eh_dispatch_allowed_cfg_superedge): Drop. (is_a_helper ::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/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 --- gcc/Makefile.in | 4 + gcc/analyzer/analyzer-logging.h | 41 + gcc/analyzer/analyzer.cc | 10 + gcc/analyzer/analyzer.opt | 12 +- gcc/analyzer/bounds-checking.cc | 2 +- gcc/analyzer/call-details.h | 2 + gcc/analyzer/call-info.cc | 17 +- gcc/analyzer/call-info.h | 3 +- gcc/analyzer/call-string.cc | 104 +- gcc/analyzer/call-string.h | 62 +- gcc/analyzer/call-summary.cc | 6 +- gcc/analyzer/checker-event.cc | 285 +- gcc/analyzer/checker-event.h | 107 +- gcc/analyzer/common.h | 36 +- gcc/analyzer/constraint-manager.cc | 47 - gcc/analyzer/constraint-manager.h | 19 +- gcc/analyzer/diagnostic-manager.cc | 813 +--- gcc/analyzer/diagnostic-manager.h | 106 +- gcc/analyzer/engine.cc | 3273 +++++------------ gcc/analyzer/event-loc-info.h | 3 + gcc/analyzer/exploded-graph.h | 228 +- gcc/analyzer/feasible-graph.cc | 31 - gcc/analyzer/impl-sm-context.h | 289 ++ gcc/analyzer/infinite-loop.cc | 150 +- gcc/analyzer/infinite-recursion.cc | 39 +- gcc/analyzer/kf-lang-cp.cc | 57 +- gcc/analyzer/ops.cc | 2391 ++++++++++++ gcc/analyzer/ops.h | 1007 +++++ gcc/analyzer/pending-diagnostic.cc | 15 +- gcc/analyzer/pending-diagnostic.h | 15 +- gcc/analyzer/program-point.cc | 603 +-- gcc/analyzer/program-point.h | 200 +- gcc/analyzer/program-state.cc | 174 +- gcc/analyzer/program-state.h | 16 - gcc/analyzer/region-model-manager.cc | 46 +- gcc/analyzer/region-model-manager.h | 2 +- gcc/analyzer/region-model.cc | 726 +--- gcc/analyzer/region-model.h | 123 +- gcc/analyzer/region.cc | 9 +- gcc/analyzer/sm-fd.cc | 321 +- gcc/analyzer/sm-file.cc | 33 +- gcc/analyzer/sm-malloc.cc | 274 +- gcc/analyzer/sm-pattern-test.cc | 11 +- gcc/analyzer/sm-sensitive.cc | 16 +- gcc/analyzer/sm-signal.cc | 8 +- gcc/analyzer/sm-taint.cc | 80 +- gcc/analyzer/sm.cc | 10 +- gcc/analyzer/sm.h | 64 +- gcc/analyzer/state-purge.cc | 820 ++--- gcc/analyzer/state-purge.h | 127 +- gcc/analyzer/store.cc | 22 +- gcc/analyzer/supergraph-fixup-locations.cc | 123 + gcc/analyzer/supergraph-manipulation.h | 73 + gcc/analyzer/supergraph-simplify.cc | 317 ++ gcc/analyzer/supergraph-sorting.cc | 266 ++ gcc/analyzer/supergraph.cc | 1704 +++------ gcc/analyzer/supergraph.h | 687 +--- gcc/analyzer/svalue.cc | 31 +- gcc/analyzer/svalue.h | 22 +- gcc/analyzer/varargs.cc | 68 +- gcc/digraph.h | 52 +- gcc/doc/analyzer.texi | 116 +- gcc/doc/invoke.texi | 29 +- gcc/gdbhooks.py | 2 +- gcc/system.h | 3 + .../analyzer/allocation-size-multiline-1.c | 10 +- .../c-c++-common/analyzer/bzip2-arg-parse-1.c | 5 +- .../analyzer/coreutils-cksum-pr108664.c | 4 +- .../analyzer/coreutils-group_number.c | 43 + .../c-c++-common/analyzer/data-model-20.c | 4 +- .../deref-before-check-qemu-qtest_rsp_args.c | 14 +- .../c-c++-common/analyzer/dot-output.c | 8 +- .../analyzer/fd-symbolic-socket.c | 9 +- .../c-c++-common/analyzer/fibonacci.c | 2 +- .../analyzer/flex-with-call-summaries.c | 2 +- .../analyzer/flex-without-call-summaries.c | 6 +- .../analyzer/infinite-recursion-5.c | 3 + .../analyzer/infinite-recursion-pr108524-2.c | 2 +- .../c-c++-common/analyzer/invalid-shift-1.c | 4 +- gcc/testsuite/c-c++-common/analyzer/loop-4.c | 6 +- .../analyzer/loop-n-down-to-1-by-1.c | 4 +- gcc/testsuite/c-c++-common/analyzer/loop.c | 2 +- .../analyzer/out-of-bounds-coreutils.c | 2 +- gcc/testsuite/c-c++-common/analyzer/paths-4.c | 10 +- .../c-c++-common/analyzer/pr94362-1.c | 3 - .../c-c++-common/analyzer/pr94851-2.c | 2 +- .../c-c++-common/analyzer/pr96650-1-notrans.c | 1 + gcc/testsuite/c-c++-common/analyzer/pr98628.c | 2 +- .../c-c++-common/analyzer/pr99774-1.c | 2 +- .../c-c++-common/analyzer/pragma-2.c | 2 +- .../c-c++-common/analyzer/realloc-1.c | 2 +- gcc/testsuite/c-c++-common/analyzer/sock-1.c | 1 + .../c-c++-common/analyzer/sprintf-2.c | 3 +- .../c-c++-common/analyzer/sprintf-concat.c | 4 +- .../c-c++-common/analyzer/stdarg-sentinel-1.c | 2 - .../c-c++-common/analyzer/strncpy-1.c | 4 +- .../c-c++-common/analyzer/strstr-1.c | 4 +- gcc/testsuite/g++.dg/analyzer/analyzer.exp | 2 +- ...er-show-events-in-system-headers-default.C | 2 - ...nalyzer-show-events-in-system-headers-no.C | 2 - .../fanalyzer-show-events-in-system-headers.C | 2 +- gcc/testsuite/g++.dg/analyzer/pr94028.C | 4 +- gcc/testsuite/g++.dg/analyzer/pr96641.C | 2 +- .../gcc.dg/analyzer/CWE-131-examples.c | 2 +- gcc/testsuite/gcc.dg/analyzer/abs-1.c | 2 +- .../gcc.dg/analyzer/analyzer-decls.h | 60 +- gcc/testsuite/gcc.dg/analyzer/analyzer.exp | 2 +- .../gcc.dg/analyzer/boxed-malloc-1.c | 8 +- .../gcc.dg/analyzer/call-summaries-2.c | 2 +- .../gcc.dg/analyzer/combined-conditionals-1.c | 2 +- .../gcc.dg/analyzer/compound-assignment-2.c | 1 + .../gcc.dg/analyzer/compound-assignment-3.c | 1 + .../gcc.dg/analyzer/conditionals-3.c | 2 +- gcc/testsuite/gcc.dg/analyzer/data-model-1.c | 4 +- gcc/testsuite/gcc.dg/analyzer/data-model-15.c | 2 +- gcc/testsuite/gcc.dg/analyzer/data-model-17.c | 2 +- .../gcc.dg/analyzer/data-model-20a.c | 2 +- gcc/testsuite/gcc.dg/analyzer/data-model-7.c | 2 +- .../analyzer/doom-d_main-IdentifyVersion.c | 42 +- .../gcc.dg/analyzer/doom-s_sound-pr108867.c | 2 +- gcc/testsuite/gcc.dg/analyzer/edges-1.c | 5 +- gcc/testsuite/gcc.dg/analyzer/error-1.c | 8 +- gcc/testsuite/gcc.dg/analyzer/explode-1.c | 6 +- gcc/testsuite/gcc.dg/analyzer/explode-2.c | 2 +- gcc/testsuite/gcc.dg/analyzer/explode-3.c | 6 +- .../gcc.dg/analyzer/fd-datagram-socket.c | 9 +- .../fd-glibc-byte-stream-connection-server.c | 2 +- .../gcc.dg/analyzer/fd-stream-socket.c | 9 +- gcc/testsuite/gcc.dg/analyzer/malloc-1.c | 6 +- .../gcc.dg/analyzer/malloc-many-paths-2.c | 11 +- .../gcc.dg/analyzer/malloc-paths-10.c | 2 +- .../gcc.dg/analyzer/malloc-vs-local-4.c | 2 +- .../gcc.dg/analyzer/memset-CVE-2017-18549-1.c | 4 +- .../gcc.dg/analyzer/null-deref-pr102671-1.c | 2 +- .../gcc.dg/analyzer/null-deref-pr102671-2.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr101143.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr101837.c | 4 +- .../gcc.dg/analyzer/pr101983-not-main.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr103892.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr104224.c | 17 +- .../gcc.dg/analyzer/pr104434-nonconst.c | 4 +- .../analyzer/pr93032-mztools-signed-char.c | 2 +- .../analyzer/pr93032-mztools-unsigned-char.c | 2 +- .../pr93355-localealias-feasibility-2.c | 2 +- .../gcc.dg/analyzer/pr93355-localealias.c | 6 +- gcc/testsuite/gcc.dg/analyzer/pr94579.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr98599-a.c | 4 +- gcc/testsuite/gcc.dg/analyzer/pr99771-1.c | 32 +- gcc/testsuite/gcc.dg/analyzer/pr99774-2.c | 8 +- gcc/testsuite/gcc.dg/analyzer/sensitive-1.c | 12 +- .../gcc.dg/analyzer/state-diagram-1-sarif.py | 2 +- gcc/testsuite/gcc.dg/analyzer/stdarg-1.c | 2 +- gcc/testsuite/gcc.dg/analyzer/strcmp-1.c | 8 +- gcc/testsuite/gcc.dg/analyzer/strcpy-1.c | 2 +- .../gcc.dg/analyzer/switch-enum-taint-1.c | 2 +- gcc/testsuite/gcc.dg/analyzer/switch.c | 4 +- gcc/testsuite/gcc.dg/analyzer/taint-assert.c | 2 +- .../gcc.dg/analyzer/taint-write-offset-1.c | 2 +- .../analyzer/torture/analyzer-torture.exp | 2 +- .../gcc.dg/analyzer/torture/boxed-ptr-1.c | 2 +- .../torture/fold-ptr-arith-pr105784.c | 2 +- .../gcc.dg/analyzer/torture/loop-inc-ptr-1.c | 2 +- .../gcc.dg/analyzer/torture/pr102225.c | 3 +- .../gcc.dg/analyzer/torture/pr93379.c | 2 + .../gcc.dg/analyzer/torture/stdarg-4.c | 1 + gcc/testsuite/gcc.dg/analyzer/untracked-1.c | 2 +- .../gcc.dg/analyzer/use-after-free.c | 2 +- gcc/testsuite/gcc.dg/analyzer/zlib-3.c | 6 +- .../gcc.dg/plugin/analyzer_cpython_plugin.cc | 96 +- .../gcc.dg/plugin/analyzer_gil_plugin.cc | 25 +- .../gcc.dg/plugin/infoleak-CVE-2011-1078-1.c | 4 + .../gcc.dg/plugin/infoleak-CVE-2011-1078-2.c | 4 +- .../gcc.dg/plugin/infoleak-CVE-2017-18549-1.c | 8 +- gcc/testsuite/gdc.dg/analyzer/analyzer.exp | 2 +- .../gfortran.dg/analyzer/analyzer.exp | 2 +- .../gfortran.dg/analyzer/uninit-pr63311.f90 | 1 + 176 files changed, 8441 insertions(+), 8602 deletions(-) create mode 100644 gcc/analyzer/impl-sm-context.h create mode 100644 gcc/analyzer/ops.cc create mode 100644 gcc/analyzer/ops.h create mode 100644 gcc/analyzer/supergraph-fixup-locations.cc create mode 100644 gcc/analyzer/supergraph-manipulation.h create mode 100644 gcc/analyzer/supergraph-simplify.cc create mode 100644 gcc/analyzer/supergraph-sorting.cc create mode 100644 gcc/testsuite/c-c++-common/analyzer/coreutils-group_number.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 2c3194e7d1e..19527023a69 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1358,6 +1358,7 @@ ANALYZER_OBJS = \ 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 \ @@ -1379,6 +1380,9 @@ ANALYZER_OBJS = \ 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 \ diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h index e85d293101d..cef33a78ae0 100644 --- a/gcc/analyzer/analyzer-logging.h +++ b/gcc/analyzer/analyzer-logging.h @@ -139,6 +139,47 @@ log_scope::~log_scope () } } +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). diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index 9f6f7b44224..1cfd6e4c58b 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -469,6 +469,16 @@ is_cxa_rethrow_p (const gcall &call) 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). */ diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index 611b987daec..4aa3afbea5d 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -287,8 +287,8 @@ Common Var(flag_analyzer_debug_text_art) Init(0) 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) @@ -298,6 +298,10 @@ fanalyzer-show-duplicate-count 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. @@ -347,8 +351,8 @@ Common RejectNegative Var(flag_dump_analyzer_stderr) 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) diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc index 7c51ca27bc5..04aaee3cdc0 100644 --- a/gcc/analyzer/bounds-checking.cc +++ b/gcc/analyzer/bounds-checking.cc @@ -1327,7 +1327,7 @@ strip_types (const svalue *sval, 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)); } diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h index c0a9118e658..272be2399fd 100644 --- a/gcc/analyzer/call-details.h +++ b/gcc/analyzer/call-details.h @@ -21,6 +21,8 @@ along with GCC; see the file COPYING3. If not see #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. */ diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc index f43114344cf..1470b95c1bd 100644 --- a/gcc/analyzer/call-info.cc +++ b/gcc/analyzer/call-info.cc @@ -58,6 +58,14 @@ custom_edge_info::update_state (program_state *state, 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 * @@ -87,7 +95,8 @@ call_info::print (pretty_printer *pp) const 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 { @@ -114,9 +123,9 @@ call_info::add_events_to_path (checker_path *emission_path, emission_path->add_event (std::make_unique (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. */ diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h index 6548d861bf2..63fd008cb62 100644 --- a/gcc/analyzer/call-info.h +++ b/gcc/analyzer/call-info.h @@ -32,7 +32,8 @@ class call_info : public custom_edge_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; } diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc index 0bac8b43734..4eae51a876f 100644 --- a/gcc/analyzer/call-string.cc +++ b/gcc/analyzer/call-string.cc @@ -39,7 +39,7 @@ along with GCC; see the file COPYING3. If not see 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; } /* call_string::element_t's inequality operator. */ @@ -52,13 +52,25 @@ call_string::element_t::operator!= (const call_string::element_t &other) const 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_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_callee->get_function (); + return m_call_op->get_gcall (); } /* Print this to PP. */ @@ -75,17 +87,18 @@ call_string::print (pretty_printer *pp) const 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 @@ -96,36 +109,25 @@ call_string::to_json () const for (const call_string::element_t &e : m_elements) { auto e_obj = std::make_unique (); - 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; @@ -160,10 +162,10 @@ call_string::count_occurrences_of_function (function *fun) const 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; } @@ -199,14 +201,10 @@ call_string::cmp (const call_string &a, /* 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; + int src_cmp = (a_node_pair.m_call_sedge->m_src->m_id + - b_node_pair.m_call_sedge->m_src->m_id); 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; i++; // TODO: test coverage for this } @@ -222,24 +220,14 @@ call_string::cmp_ptr_ptr (const void *pa, const void *pb) 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. */ @@ -252,15 +240,20 @@ call_string::validate () const 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. */ @@ -308,8 +301,9 @@ call_string::recursive_log (logger *logger) const /* 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, "[]"); diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h index f8c6a257cf5..25e0b2b7664 100644 --- a/gcc/analyzer/call-string.h +++ b/gcc/analyzer/call-string.h @@ -25,20 +25,13 @@ namespace ana { 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, @@ -49,13 +42,19 @@ class call_string 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) { } @@ -64,10 +63,14 @@ public: /* 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; @@ -76,11 +79,9 @@ public: 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; @@ -91,9 +92,7 @@ public: 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 { @@ -119,8 +118,7 @@ private: 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) @@ -129,25 +127,25 @@ private: } template 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 static inline bool is_empty (const T &entry) { - return entry.m_key.m_caller == nullptr; + return entry.m_key.m_call_sedge == nullptr; } template static inline bool is_deleted (const T &entry) { - return entry.m_key.m_caller == reinterpret_cast (1); + return entry.m_key.m_call_sedge == reinterpret_cast (1); } template 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 static inline void mark_deleted (T &entry) { - entry.m_key.m_caller = reinterpret_cast (1); + entry.m_key.m_call_sedge = reinterpret_cast (1); } }; diff --git a/gcc/analyzer/call-summary.cc b/gcc/analyzer/call-summary.cc index 14ff560b23a..7a3d4aac7c6 100644 --- a/gcc/analyzer/call-summary.cc +++ b/gcc/analyzer/call-summary.cc @@ -38,7 +38,7 @@ call_summary::get_state () const tree call_summary::get_fndecl () const { - return m_enode->get_point ().get_fndecl (); + return m_enode->get_function ()->decl; } label_text @@ -393,7 +393,7 @@ call_summary_replay::convert_svalue_from_summary_1 (const svalue *summary_sval) { const widening_svalue *widening_summary_sval = as_a (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 @@ -411,7 +411,7 @@ call_summary_replay::convert_svalue_from_summary_1 (const svalue *summary_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); } diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc index 57e36d470e8..bd62e3887a6 100644 --- a/gcc/analyzer/checker-event.cc +++ b/gcc/analyzer/checker-event.cc @@ -77,10 +77,10 @@ event_kind_to_string (enum event_kind ek) 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: @@ -379,8 +379,7 @@ region_creation_event_debug::print_desc (pretty_printer &pp) const 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) @@ -411,9 +410,8 @@ function_entry_event::get_meaning () const /* 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, @@ -421,11 +419,9 @@ state_change_event::state_change_event (const supernode *node, 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), @@ -551,43 +547,26 @@ maybe_add_sarif_properties (diagnostics::sarif_builder &builder, #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; } @@ -598,37 +577,37 @@ superedge_event::get_program_state () const 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 @@ -637,13 +616,20 @@ cfg_edge_event::cfg_edge_event (enum event_kind kind, 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. */ @@ -675,9 +661,12 @@ start_cfg_edge_event::print_desc (pretty_printer &pp) const 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, @@ -695,145 +684,16 @@ start_cfg_edge_event::print_desc (pretty_printer &pp) const 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 (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 ''. */ - - /* 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 (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 @@ -848,11 +708,8 @@ catch_cfg_edge_event::get_meaning () const 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 (); } @@ -871,14 +728,14 @@ call_event::call_event (const exploded_edge &eedge, 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; } @@ -926,19 +783,19 @@ call_event::get_program_state () const 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 @@ -959,11 +816,11 @@ return_event::print_desc (pretty_printer &pp) const 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; } diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h index fc51be10c95..f79130ffb38 100644 --- a/gcc/analyzer/checker-event.h +++ b/gcc/analyzer/checker-event.h @@ -42,8 +42,8 @@ enum class event_kind start_cfg_edge, end_cfg_edge, catch_, - call_edge, - return_edge, + call_, + return_, start_consolidated_cfg_edges, end_consolidated_cfg_edges, inlined_call, @@ -76,8 +76,8 @@ extern const char *event_kind_to_string (enum event_kind ek); 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) @@ -381,8 +381,8 @@ private: 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, @@ -407,7 +407,6 @@ public: 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; @@ -429,23 +428,14 @@ public: 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); @@ -453,8 +443,6 @@ public: 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 @@ -466,11 +454,15 @@ class cfg_edge_event : public superedge_event 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 @@ -480,22 +472,16 @@ class start_cfg_edge_event : public cfg_edge_event { public: start_cfg_edge_event (const exploded_edge &eedge, - const event_loc_info &loc_info) - : cfg_edge_event (event_kind::start_cfg_edge, eedge, loc_info) + 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); }; @@ -506,8 +492,9 @@ class end_cfg_edge_event : public cfg_edge_event { public: end_cfg_edge_event (const exploded_edge &eedge, - const event_loc_info &loc_info) - : cfg_edge_event (event_kind::end_cfg_edge, eedge, loc_info) + const event_loc_info &loc_info, + const control_flow_op *op) + : cfg_edge_event (event_kind::end_cfg_edge, eedge, loc_info, op) { } @@ -525,8 +512,9 @@ class catch_cfg_edge_event : public cfg_edge_event 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) { } @@ -545,6 +533,23 @@ private: 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 @@ -561,17 +566,27 @@ public: 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, @@ -582,8 +597,26 @@ public: 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 diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h index ac3048c796e..a2d903cbe17 100644 --- a/gcc/analyzer/common.h +++ b/gcc/analyzer/common.h @@ -23,6 +23,7 @@ along with GCC; see the file COPYING3. If not see #include "config.h" #define INCLUDE_MAP +#define INCLUDE_SET #define INCLUDE_STRING #define INCLUDE_VECTOR #include "system.h" @@ -49,14 +50,6 @@ namespace ana { 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; @@ -119,14 +112,13 @@ class checker_event; 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; @@ -145,6 +137,7 @@ class call_summary; class call_summary_replay; struct per_function_data; struct interesting_t; +class uncertainty_t; class feasible_node; @@ -163,6 +156,12 @@ extern int tree_cmp (const void *p1, const void *p2); 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. */ @@ -296,6 +295,15 @@ class known_function 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; @@ -388,6 +396,10 @@ public: /* 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, @@ -400,7 +412,8 @@ public: 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, @@ -485,6 +498,7 @@ extern bool is_longjmp_call_p (const gcall &call); 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); diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index c8cc71593cc..4b8dae029dd 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -930,53 +930,6 @@ bounded_ranges_manager::consolidate (bounded_ranges *inst) 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 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. */ diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h index 38686b7e696..4e3b3967cbc 100644 --- a/gcc/analyzer/constraint-manager.h +++ b/gcc/analyzer/constraint-manager.h @@ -172,20 +172,13 @@ template <> struct default_hash_traits 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, @@ -200,15 +193,11 @@ public: 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 @@ -241,10 +230,6 @@ private: }; typedef hash_map map_t; map_t m_map; - - typedef hash_map edge_cache_t; - edge_cache_t m_edge_cache; }; /* An equivalence class within a constraint manager: a set of diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index d3ed085b712..b7131e2ab09 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -52,6 +52,39 @@ namespace ana { 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 +pending_location::to_json () const +{ + auto ploc_obj = std::make_unique (); + + 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. */ @@ -76,7 +109,6 @@ public: std::unique_ptr 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 *out_problem); @@ -86,7 +118,6 @@ private: std::unique_ptr explore_feasible_paths (const exploded_node *target_enode, - const gimple *target_stmt, const pending_diagnostic &pd, const char *desc, unsigned diag_idx); bool @@ -94,7 +125,6 @@ private: 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 *out_best_path) const; @@ -116,12 +146,9 @@ private: /* 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). @@ -134,8 +161,7 @@ private: Write any feasibility_problem to *OUT_PROBLEM. */ std::unique_ptr -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 *out_problem) @@ -143,10 +169,10 @@ epath_finder::get_best_epath (const exploded_node *enode, 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. @@ -160,12 +186,12 @@ epath_finder::get_best_epath (const exploded_node *enode, if (logger) logger->log ("trying to find shortest feasible path"); if (std::unique_ptr 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; } @@ -174,7 +200,7 @@ epath_finder::get_best_epath (const exploded_node *enode, 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; } } @@ -188,14 +214,15 @@ epath_finder::get_best_epath (const exploded_node *enode, if (logger) logger->log ("trying to find shortest path ignoring feasibility"); gcc_assert (m_sep); - std::unique_ptr epath - = std::make_unique (m_sep->get_shortest_path (enode)); + auto epath + = std::make_unique + (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 @@ -203,7 +230,7 @@ epath_finder::get_best_epath (const exploded_node *enode, 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"); } @@ -330,9 +357,6 @@ private: 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. @@ -376,7 +400,6 @@ private: std::unique_ptr epath_finder::explore_feasible_paths (const exploded_node *target_enode, - const gimple *target_stmt, const pending_diagnostic &pd, const char *desc, unsigned diag_idx) { @@ -419,7 +442,7 @@ epath_finder::explore_feasible_paths (const exploded_node *target_enode, { 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. */ @@ -464,7 +487,6 @@ process_worklist_item (feasible_worklist *worklist, 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 *out_best_path) const @@ -523,7 +545,7 @@ process_worklist_item (feasible_worklist *worklist, " (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" @@ -654,18 +676,14 @@ epath_finder::dump_feasible_path (const exploded_node *target_enode, /* 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 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), @@ -674,7 +692,13 @@ saved_diagnostic::saved_diagnostic (const state_machine *sm, { /* 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 @@ -692,10 +716,9 @@ saved_diagnostic::operator== (const saved_diagnostic &other) const 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) @@ -722,8 +745,7 @@ saved_diagnostic::add_event (std::unique_ptr event) /* 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, @@ -737,8 +759,7 @@ saved_diagnostic::to_json () const 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) @@ -748,16 +769,6 @@ saved_diagnostic::to_json () const 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 m_notes; - */ - return sd_obj; } @@ -792,12 +803,6 @@ saved_diagnostic::dump_as_dot_node (pretty_printer *pp) const } 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) @@ -823,10 +828,8 @@ saved_diagnostic::dump_as_dot_node (pretty_printer *pp) const } } -/* 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 @@ -836,7 +839,7 @@ saved_diagnostic::calc_best_epath (epath_finder *pf) 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); @@ -845,15 +848,6 @@ saved_diagnostic::calc_best_epath (epath_finder *pf) 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; } @@ -976,9 +970,9 @@ compatible_epath_p (const exploded_path *lhs_path, 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; @@ -1022,14 +1016,7 @@ maybe_add_sarif_properties (diagnostics::sarif_object &result_obj) const #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) @@ -1115,20 +1102,6 @@ private: 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. */ @@ -1145,7 +1118,7 @@ diagnostic_manager::diagnostic_manager (logger *logger, engine *eng, 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, @@ -1158,33 +1131,37 @@ diagnostic_manager::add_diagnostic (const state_machine *sm, 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; } @@ -1193,11 +1170,12 @@ diagnostic_manager::add_diagnostic (const state_machine *sm, 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 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. */ @@ -1257,32 +1235,24 @@ class dedupe_key { 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. */ @@ -1308,7 +1278,6 @@ public: } const saved_diagnostic &m_sd; - const gimple *m_stmt; location_t m_loc; }; @@ -1384,6 +1353,14 @@ public: 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)) { @@ -1512,8 +1489,8 @@ diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg) 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) @@ -1574,7 +1551,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, { 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 (); @@ -1600,19 +1577,10 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, 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); } @@ -1626,9 +1594,8 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, 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 ()); @@ -1637,7 +1604,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, 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)) @@ -1700,7 +1667,7 @@ diagnostic_manager::build_emission_path (const path_builder &pb, 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 (), @@ -1810,19 +1777,13 @@ public: 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 (supernode, + (std::make_unique (m_eedge.m_src, stmt, - stack_depth, sm, nullptr, src_sm_val, @@ -1841,33 +1802,15 @@ public: { 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 (supernode, + (std::make_unique (m_eedge.m_src, stmt, - stack_depth, sm, sval, src_sm_val, @@ -1944,185 +1887,6 @@ for_each_state_change (const program_state &src_state, 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 (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 (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) final override - { - } - void warn (const supernode *, const gimple *, - const svalue *, std::unique_ptr) 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 (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. */ @@ -2172,113 +1936,57 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, 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 (stmt); - if (call && is_setjmp_call_p (*call)) - emission_path->add_event - (std::make_unique - (event_loc_info (stmt->location, - dst_point.get_fndecl (), - dst_stack_depth), - dst_node, - *call)); - else - emission_path->add_event - (std::make_unique (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 (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 @@ -2408,122 +2116,6 @@ diagnostic_manager::significant_edge_p (const path_builder &pb, 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 - (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 - (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 - (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 - (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 (eedge.m_sedge); - - const gcall &call_stmt = return_edge->get_call_stmt (); - emission_path->add_event - (std::make_unique - (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). @@ -2742,7 +2334,7 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, /* 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 @@ -2753,18 +2345,16 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, 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) @@ -2783,7 +2373,7 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, } break; - case event_kind::return_edge: + case event_kind::return_: { if (sval) { @@ -2795,20 +2385,19 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, = 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) { @@ -3118,15 +2707,16 @@ diagnostic_manager::consolidate_conditions (checker_path *path) const 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) @@ -3141,16 +2731,17 @@ diagnostic_manager::consolidate_conditions (checker_path *path) const 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; diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h index d9cf109080f..bb32e3852b0 100644 --- a/gcc/analyzer/diagnostic-manager.h +++ b/gcc/analyzer/diagnostic-manager.h @@ -21,17 +21,65 @@ along with GCC; see the file COPYING3. If not see #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 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 m_fixer_for_epath; +}; + +extern std::unique_ptr +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 d, @@ -70,13 +118,11 @@ public: 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 m_stmt_finder; - location_t m_loc; + pending_location m_ploc; tree m_var; const svalue *m_sval; state_machine::state_t m_state; @@ -102,46 +148,6 @@ private: 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 @@ -161,13 +167,13 @@ public: std::unique_ptr 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 d); - bool add_diagnostic (const pending_location &ploc, + bool add_diagnostic (pending_location &&ploc, std::unique_ptr d); void add_note (std::unique_ptr pn); @@ -212,10 +218,6 @@ private: 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, diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 9d22950d79b..714e6431cd6 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -34,6 +34,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "tree-dfa.h" +#include "gimple-predict.h" #include "text-art/dump.h" @@ -56,6 +57,7 @@ along with GCC; see the file COPYING3. If not see #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. */ @@ -73,14 +75,12 @@ impl_region_model_context (exploded_graph &eg, 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), @@ -97,7 +97,6 @@ impl_region_model_context (program_state *state, 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), @@ -106,25 +105,14 @@ impl_region_model_context (program_state *state, } bool -impl_region_model_context::warn (std::unique_ptr d, - const stmt_finder *custom_finder) +impl_region_model_context::warn_at (std::unique_ptr 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 @@ -279,271 +267,6 @@ setjmp_svalue::get_enode_index () const 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 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 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 ''" - 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 (m_enode_for_diag), - m_sm_idx); - } - - tree is_zero_assignment (const gimple *stmt) final override - { - const gassign *assign_stmt = dyn_cast (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, @@ -579,104 +302,252 @@ 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 clone () const final override - { - return std::make_unique (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 (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 (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 (&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 +make_ploc_fixer_for_epath_for_leak_diagnostic (const exploded_graph &eg, + tree var) +{ + return std::make_unique (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'" @@ -831,13 +702,16 @@ returning_from_function_p (const supernode *snode) 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. @@ -851,7 +725,7 @@ returning_from_function_p (const supernode *snode) return _val; EXIT BB.*/ - if (++count > 3) + if (++count > 4) return false; } } @@ -907,7 +781,6 @@ impl_region_model_context::on_state_leak (const state_machine &sm, 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". */ @@ -928,12 +801,11 @@ impl_region_model_context::on_state_leak (const state_machine &sm, 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 (*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)); } } @@ -957,12 +829,7 @@ impl_region_model_context::on_condition (const svalue *lhs, 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); } } @@ -984,11 +851,7 @@ impl_region_model_context::on_bounded_ranges (const svalue &sval, 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); } } @@ -1025,7 +888,7 @@ impl_region_model_context::on_phi (const gphi *phi, tree rhs) 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); } } @@ -1059,6 +922,15 @@ impl_region_model_context::maybe_did_work () *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. */ @@ -1198,6 +1070,7 @@ exploded_node::status_to_str (enum status s) 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"; } @@ -1213,23 +1086,6 @@ exploded_node::exploded_node (const point_and_state &ps, 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. */ @@ -1297,12 +1153,21 @@ exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &args) const 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); @@ -1336,31 +1201,6 @@ exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &args) const 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 @@ -1460,422 +1300,69 @@ fndecl_has_gimple_body_p (tree fndecl) 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 (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 +{ +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 (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 (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 (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 (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 -{ -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, @@ -1963,21 +1450,22 @@ exploded_node::on_longjmp (exploded_graph &eg, 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 (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. */ @@ -1988,18 +1476,13 @@ exploded_node::on_longjmp (exploded_graph &eg, 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) @@ -2111,7 +1594,8 @@ public: } 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 (); @@ -2160,7 +1644,8 @@ public: } 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 (); @@ -2224,9 +1709,8 @@ exploded_graph::unwind_from_exception (exploded_node &thrown_enode, 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 (), @@ -2278,8 +1762,7 @@ exploded_graph::unwind_from_exception (exploded_node &thrown_enode, { /* 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 @@ -2313,6 +1796,7 @@ exploded_graph::unwind_from_exception (exploded_node &thrown_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) @@ -2333,12 +1817,11 @@ exploded_node::on_throw (exploded_graph &eg, const svalue *tinfo_sval = cd.get_arg_svalue (1); type = tinfo_sval->maybe_get_type_from_typeinfo (); } + auto throw_edge_info = std::make_unique (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 @@ -2355,22 +1838,6 @@ exploded_node::on_throw (exploded_graph &eg, 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. @@ -2382,7 +1849,7 @@ exploded_node::detect_leaks (exploded_graph &eg) { 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. */ @@ -2403,7 +1870,7 @@ exploded_node::detect_leaks (exploded_graph &eg) 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, @@ -2437,56 +1904,92 @@ exploded_node::dump_succs_and_preds (FILE *outf) const } } -/* 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 (eedge, - event_loc_info (m_dynamic_call.location, - dest_point.get_fndecl (), - dest_stack_depth))); - else - emission_path->add_event - (std::make_unique (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 + (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. */ @@ -2521,7 +2024,8 @@ rewind_info_t::update_model (region_model *model, 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 (); @@ -2585,34 +2089,20 @@ exploded_edge::dump_dot_label (pretty_printer *pp) const 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); @@ -2622,7 +2112,7 @@ exploded_edge::dump_dot_label (pretty_printer *pp) const 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"); } @@ -2651,17 +2141,33 @@ exploded_edge::to_json () const 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. */ @@ -2670,11 +2176,7 @@ void 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 (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); @@ -2685,18 +2187,14 @@ stats::log (logger *logger) const 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 (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. */ @@ -2704,10 +2202,7 @@ stats::dump (FILE *out) const 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. */ @@ -2728,17 +2223,17 @@ per_function_data::add_call_summary (exploded_node *node) 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 (); @@ -2749,11 +2244,22 @@ strongly_connected_components (const supergraph &sg, logger *logger) 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); } } @@ -2763,7 +2269,7 @@ std::unique_ptr strongly_connected_components::to_json () const { auto scc_arr = std::make_unique (); - 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 (get_scc_id (i))); return scc_arr; } @@ -2772,32 +2278,32 @@ strongly_connected_components::to_json () const 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) @@ -2805,19 +2311,23 @@ strongly_connected_components::strong_connect (unsigned index) /* 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); } } @@ -2940,18 +2450,13 @@ worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb) 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); @@ -3006,15 +2511,12 @@ exploded_graph::exploded_graph (const supergraph &sg, logger *logger, 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. */ @@ -3121,16 +2623,19 @@ public: 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 @@ -3231,6 +2736,14 @@ exploded_graph::get_or_create_node (const program_point &point, 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 ()); @@ -3280,7 +2793,7 @@ exploded_graph::get_or_create_node (const program_point &point, = 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; @@ -3355,13 +2868,9 @@ exploded_graph::get_or_create_node (const program_point &point, /* 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) { @@ -3373,8 +2882,8 @@ exploded_graph::get_or_create_node (const program_point &point, 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 (); } @@ -3443,7 +2952,7 @@ exploded_graph::get_or_create_per_call_string_data (const call_string &cs) 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; @@ -3571,16 +3080,18 @@ public: 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))'" */ @@ -3745,16 +3256,15 @@ exploded_graph::process_worklist () { 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 @@ -3765,9 +3275,6 @@ exploded_graph::process_worklist () { 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) @@ -3890,45 +3397,37 @@ exploded_graph::process_worklist () 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. @@ -3945,7 +3444,7 @@ exploded_graph::process_worklist () 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 @@ -3962,20 +3461,26 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) }; 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 enodes; enodes.safe_push (enode); while (exploded_node *enode_2 = m_worklist.peek_next ()) @@ -3985,16 +3490,20 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) 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. */ @@ -4002,12 +3511,13 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) 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 items (enodes.length ()); @@ -4020,19 +3530,10 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) 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); } @@ -4106,275 +3607,24 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) { 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 (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 (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 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 (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 & 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 m_state_at_bifurcation; - - auto_vec 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 -{ -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. @@ -4396,9 +3646,8 @@ exploded_graph::process_node (exploded_node *node) /* 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) @@ -4412,407 +3661,122 @@ exploded_graph::process_node (exploded_node *node) 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 (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 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 (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 (*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); + } } } @@ -4830,8 +3794,10 @@ exploded_graph::get_or_create_function_stats (function *fn) 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; @@ -4864,8 +3830,8 @@ exploded_graph::print_bar_charts (pretty_printer *pp) const enodes_per_function.print (pp); /* Accumulate number of enodes per supernode. */ - auto_vec enodes_per_supernode (m_sg.num_nodes ()); - for (int i = 0; i < m_sg.num_nodes (); i++) + auto_vec 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; @@ -4874,12 +3840,12 @@ exploded_graph::print_bar_charts (pretty_printer *pp) const 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 excess_enodes_per_supernode (m_sg.num_nodes ()); - for (int i = 0; i < m_sg.num_nodes (); i++) + auto_vec 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) @@ -4889,7 +3855,7 @@ exploded_graph::print_bar_charts (pretty_printer *pp) const 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; } @@ -4905,18 +3871,18 @@ exploded_graph::print_bar_charts (pretty_printer *pp) const 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) @@ -4986,36 +3952,6 @@ exploded_graph::dump_stats (FILE *out) const 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 @@ -5061,8 +3997,7 @@ exploded_graph::to_json () const 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 m_PK_AFTER_SUPERNODE_per_snode; */ + call_string_data_map_t m_per_call_string_data; */ return egraph_obj; } @@ -5091,16 +4026,28 @@ exploded_path::find_stmt_backwards (const gimple *search_stmt, 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; } @@ -5145,10 +4092,7 @@ exploded_path::feasible_p (logger *logger, { 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 (edge_idx, *eedge, - last_stmt, std::move (rc)); } return false; @@ -5296,119 +4240,68 @@ maybe_update_for_edge (logger *logger, { 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 (stmt)) - m_model.on_assignment (assign, nullptr); - else if (const gasm *asm_stmt = dyn_cast (stmt)) - m_model.on_asm_stmt (asm_stmt, nullptr); - else if (const gcall *call = dyn_cast (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 (stmt)) - m_model.on_return (return_, nullptr); + return true; } /* Dump this object to PP. */ @@ -5451,11 +4344,11 @@ public: 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; @@ -5481,7 +4374,7 @@ public: = *(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: @@ -5764,15 +4657,13 @@ exploded_graph::dump_exploded_nodes () const 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 ()); @@ -5845,8 +4736,8 @@ exploded_graph::dump_exploded_nodes () const } /* 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 @@ -5861,89 +4752,94 @@ exploded_graph::dump_exploded_nodes () const hash_set 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 (stmt)) - if (is_special_named_call_p (*call, "__analyzer_dump_exploded_nodes", - 1)) + auto_vec processed_enodes; + auto_vec merger_enodes; + auto_vec 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 processed_enodes; - auto_vec merger_enodes; - auto_vec 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); } } + } } } @@ -5985,261 +4881,11 @@ exploded_graph::on_escaped_function (tree fndecl) } } -/* 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 -{ - 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 -{ -public: - viz_callgraph_edge (viz_callgraph_node *src, viz_callgraph_node *dest) - : dedge (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 -{ -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 m_map; -}; - -/* Placeholder subclass of cluster. */ - -class viz_callgraph_cluster : public cluster -{ -}; - -/* 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 ".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. */ @@ -6250,110 +4896,89 @@ public: 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 ()); + /* 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 *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, "UNREACHED"); + 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, ""); + 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, "UNREACHED"); + 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=<"); + print_enode (gv, m_eg.m_nodes[0]); + pp_string (pp, "
>];\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. @@ -6367,7 +4992,7 @@ private: pretty_printer *pp = gv->get_pp (); pp_printf (pp, "", enode->get_dot_fillcolor ()); - pp_printf (pp, ""); + pp_printf (pp, "
", enode->m_index); gv->begin_trtd (); pp_printf (pp, "EN: %i", enode->m_index); switch (enode->get_status ()) @@ -6433,10 +5058,6 @@ private: 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. */ } @@ -6445,7 +5066,7 @@ private: } const exploded_graph &m_eg; - auto_delete_vec > m_enodes_per_snodes; + std::vector > m_enodes_per_snode_id; }; /* Implement -fdump-analyzer-json. */ @@ -6517,6 +5138,29 @@ private: 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 @@ -6537,32 +5181,38 @@ impl_run_checkers (logger *logger) 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); } @@ -6620,18 +5270,11 @@ impl_run_checkers (logger *logger) 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) diff --git a/gcc/analyzer/event-loc-info.h b/gcc/analyzer/event-loc-info.h index bf772eef4ee..9197541bb13 100644 --- a/gcc/analyzer/event-loc-info.h +++ b/gcc/analyzer/event-loc-info.h @@ -31,6 +31,9 @@ struct event_loc_info : 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; diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 1d31097ec9c..52e6bf09d76 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/sm.h" #include "analyzer/program-state.h" #include "analyzer/diagnostic-manager.h" +#include "analyzer/region-model.h" namespace ana { @@ -47,10 +48,7 @@ class impl_region_model_context : public region_model_context 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, @@ -58,8 +56,9 @@ class impl_region_model_context : public region_model_context uncertainty_t *uncertainty, logger *logger = nullptr); - bool warn (std::unique_ptr d, - const stmt_finder *custom_finder = nullptr) final override; + bool + warn_at (std::unique_ptr d, + pending_location &&ploc) final override; void add_note (std::unique_ptr pn) final override; void add_event (std::unique_ptr event) final override; void on_svalue_leak (const svalue *) override; @@ -118,13 +117,15 @@ class impl_region_model_context : public region_model_context 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; @@ -224,7 +225,7 @@ class exploded_node : public dnode 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); @@ -247,80 +248,16 @@ class exploded_node : public dnode std::unique_ptr 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); @@ -329,6 +266,10 @@ class exploded_node : public dnode { return get_point ().get_supernode (); } + location_t get_location () const + { + return get_point ().get_location (); + } function *get_function () const { return get_point ().get_function (); @@ -337,8 +278,6 @@ class exploded_node : public dnode { 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 (); } @@ -414,6 +353,9 @@ class exploded_edge : public dedge 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); @@ -433,37 +375,70 @@ private: 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). */ @@ -487,18 +462,21 @@ public: 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 @@ -531,7 +509,7 @@ struct stats 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; @@ -686,7 +664,6 @@ struct per_function_data auto_vec m_summaries; }; - /* The strongly connected components of a supergraph. In particular, this allows us to compute a partial ordering of supernodes. */ @@ -709,15 +686,15 @@ private: 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 m_stack; @@ -744,7 +721,7 @@ public: 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 to_json () const; @@ -780,7 +757,7 @@ private: 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; @@ -829,17 +806,9 @@ public: 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, @@ -863,7 +832,6 @@ public: 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); @@ -880,7 +848,6 @@ public: 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 to_json () const; @@ -954,8 +921,6 @@ private: call_string_data_map_t m_per_call_string_data; - auto_vec 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 m_functions_with_enodes; @@ -996,17 +961,15 @@ class feasibility_problem public: feasibility_problem (unsigned eedge_idx, const exploded_edge &eedge, - const gimple *last_stmt, std::unique_ptr 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 m_rc; }; @@ -1028,9 +991,10 @@ public: const exploded_edge *eedge, region_model_context *ctxt, std::unique_ptr *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; @@ -1044,20 +1008,6 @@ private: typedef shortest_paths 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 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 diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc index 25a97e7c12c..8d424f95d56 100644 --- a/gcc/analyzer/feasible-graph.cc +++ b/gcc/analyzer/feasible-graph.cc @@ -81,7 +81,6 @@ feasible_node::dump_dot (graphviz_out *gv, 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); @@ -90,36 +89,6 @@ feasible_node::dump_dot (graphviz_out *gv, 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. */ diff --git a/gcc/analyzer/impl-sm-context.h b/gcc/analyzer/impl-sm-context.h new file mode 100644 index 00000000000..1077f37ea88 --- /dev/null +++ b/gcc/analyzer/impl-sm-context.h @@ -0,0 +1,289 @@ +/* Concrete implementation of sm_context. + Copyright (C) 2019-2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 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 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 ''" + 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 (m_enode_for_diag), + m_sm_idx); + } + + tree is_zero_assignment (const gimple *stmt) final override + { + const gassign *assign_stmt = dyn_cast (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 */ diff --git a/gcc/analyzer/infinite-loop.cc b/gcc/analyzer/infinite-loop.cc index a53807c2ddb..05c23e0ef4d 100644 --- a/gcc/analyzer/infinite-loop.cc +++ b/gcc/analyzer/infinite-loop.cc @@ -108,8 +108,9 @@ class perpetual_start_cfg_edge_event : public start_cfg_edge_event { 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) { } @@ -119,10 +120,12 @@ public: 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...", @@ -143,8 +146,9 @@ class looping_back_event : public start_cfg_edge_event { 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) { } @@ -189,8 +193,8 @@ public: 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 @@ -235,9 +239,8 @@ public: 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; @@ -246,63 +249,73 @@ public: 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 + 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 + (*eedge, + loc_info_from, + control_flow_op)); + emission_path->add_event + (std::make_unique + (*eedge, + loc_info_to, + control_flow_op)); + } + } + else if (cfg_edge->flags & EDGE_TRUE_VALUE) + { + emission_path->add_event + (std::make_unique (*eedge, - loc_info_from)); - emission_path->add_event - (std::make_unique + loc_info_from, + control_flow_op)); + emission_path->add_event + (std::make_unique (*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 + (*eedge, + loc_info_from, + control_flow_op)); + emission_path->add_event + (std::make_unique + (*eedge, + loc_info_to, + control_flow_op)); + } + } - if (cfg_sedge->true_value_p ()) - { - emission_path->add_event - (std::make_unique - (*eedge, - loc_info_from)); - emission_path->add_event - (std::make_unique - (*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 - (*eedge, - loc_info_from)); + (std::make_unique (*eedge, loc_info_from, + nullptr)); emission_path->add_event (std::make_unique (*eedge, - loc_info_to)); - } - else if (cfg_sedge->back_edge_p ()) - { - emission_path->add_event - (std::make_unique (*eedge, loc_info_from)); - emission_path->add_event - (std::make_unique - (*eedge, - loc_info_to)); + loc_info_to, + nullptr)); } } } @@ -333,11 +346,9 @@ get_in_edge_back_edge (const exploded_node &enode) 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; } @@ -458,9 +469,17 @@ starts_infinite_loop_p (const exploded_node &enode, 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 @@ -555,8 +574,6 @@ exploded_graph::detect_infinite_loops () if (std::unique_ptr 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); @@ -573,10 +590,11 @@ exploded_graph::detect_infinite_loops () continue; } - pending_location ploc (enode, snode, inf_loop->m_loc); + pending_location ploc (enode, inf_loop->m_loc); auto d = std::make_unique (std::move (inf_loop)); - get_diagnostic_manager ().add_diagnostic (ploc, std::move (d)); + get_diagnostic_manager ().add_diagnostic (std::move (ploc), + std::move (d)); } } } diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc index cde3016b444..1578cd6a0af 100644 --- a/gcc/analyzer/infinite-recursion.cc +++ b/gcc/analyzer/infinite-recursion.cc @@ -177,7 +177,7 @@ public: emission_path->add_event (std::make_unique (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, @@ -187,8 +187,7 @@ public: /* 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 @@ -245,18 +244,20 @@ private: 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 (fedge->m_dest); const region_model &model = dst_fnode->get_state ().get_model (); - if (const gcond *cond_stmt = dyn_cast (last_stmt)) + if (const gcond *cond_stmt = dyn_cast (&last_stmt)) { if (expr_uses_conjured_svalue_p (model, gimple_cond_lhs (cond_stmt))) return true; @@ -264,7 +265,7 @@ private: return true; } else if (const gswitch *switch_stmt - = dyn_cast (last_stmt)) + = dyn_cast (&last_stmt)) { if (expr_uses_conjured_svalue_p (model, gimple_switch_index (switch_stmt))) @@ -304,8 +305,7 @@ private: 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) @@ -314,12 +314,7 @@ 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 @@ -618,16 +613,10 @@ exploded_graph::detect_infinite_recursion (exploded_node *enode) /* 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 (prev_entry_enode, enode, fndecl)); diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc index 01a98b0163e..ffd1e53b667 100644 --- a/gcc/analyzer/kf-lang-cp.cc +++ b/gcc/analyzer/kf-lang-cp.cc @@ -77,6 +77,26 @@ public: && 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 (); @@ -85,25 +105,16 @@ public: 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. */ @@ -138,6 +149,22 @@ public: 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 []", diff --git a/gcc/analyzer/ops.cc b/gcc/analyzer/ops.cc new file mode 100644 index 00000000000..f7dd3a6d698 --- /dev/null +++ b/gcc/analyzer/ops.cc @@ -0,0 +1,2391 @@ +/* Operations within the code being analyzed. + Copyright (C) 2019-2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 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) 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 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 (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 & 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 m_state_at_bifurcation; + + auto_vec 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(&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 (&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 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 */*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 (&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 (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 (event_loc_info (*m_point), + m_stmt, + m_sm, + sval, + from, to, + nullptr, + *m_new_state, + nullptr)); + } + + void warn (tree, std::unique_ptr) final override + { + } + void warn (const svalue *, std::unique_ptr) 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 (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 (&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 (&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 *) 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 +call_and_return_op::make (const gcall &call_stmt) +{ + if (is_special_named_call_p (call_stmt, "__analyzer_dump", 0)) + return std::make_unique (call_stmt, dump_op::dump_kind::state); + else if (is_special_named_call_p (call_stmt, "__analyzer_dump_sarif", 0)) + return std::make_unique (call_stmt, dump_op::dump_kind::sarif); + else if (is_special_named_call_p (call_stmt, "__analyzer_dump_dot", 0)) + return std::make_unique (call_stmt, dump_op::dump_kind::dot); + else if (is_special_named_call_p (call_stmt, "__analyzer_dump_state", 2)) + return std::make_unique (call_stmt, dump_op::dump_kind::state_2); + else if (is_setjmp_call_p (call_stmt)) + return std::make_unique (call_stmt); + else if (is_longjmp_call_p (call_stmt)) + return std::make_unique (call_stmt); + else if (is_cxa_throw_p (call_stmt)) + return std::make_unique (call_stmt, false); + else if (is_cxa_rethrow_p (call_stmt)) + return std::make_unique (call_stmt, true); + + return std::make_unique (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 (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 (&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 (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 + (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 (&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 (eedge, + event_loc_info (eedge.m_src), + this)); + out_path.add_event + (std::make_unique (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 *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 (get_flags () & EDGE_TRUE_VALUE) + ^ static_cast (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 ''. */ + + /* 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 (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 *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 */*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 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 *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 (index_sval); + if (unaryop->get_op () == NOP_EXPR + && is_a (unaryop->get_arg ())) + if (const initial_svalue *initvalop = (as_a + (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 (model); + return false; + } + + bool sat + = model.get_constraints ()->add_bounded_ranges (index_sval, + m_all_cases_ranges); + if (!sat && out) + *out = std::make_unique + (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::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 + (src_snode, dst_snode, + cfg_edge, eh_dispatch_stmt, + eh_reg, ehc); + } + break; + case ERT_ALLOWED_EXCEPTIONS: + return std::make_unique + (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 *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 + (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 *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 (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 (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 *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 (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 (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 (model); + return false; + } + return true; + } +} + +// class phis_for_edge_op : public operation + +std::unique_ptr +phis_for_edge_op::maybe_make (::edge cfg_in_edge) +{ + std::vector pairs = get_pairs_for_phi_along_in_edge (cfg_in_edge); + if (pairs.empty ()) + return nullptr; + + return std::make_unique (std::move (pairs), + cfg_in_edge); +} + +phis_for_edge_op::phis_for_edge_op (std::vector &&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::get_pairs_for_phi_along_in_edge (::edge cfg_in_edge) +{ + std::vector 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 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 */*out_rc*/) const +{ + hash_set 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 */ diff --git a/gcc/analyzer/ops.h b/gcc/analyzer/ops.h new file mode 100644 index 00000000000..8681e0266c4 --- /dev/null +++ b/gcc/analyzer/ops.h @@ -0,0 +1,1007 @@ +/* Operations within the code being analyzed. + Copyright (C) 2019-2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 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 + 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 */*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 *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 + clone () const final override + { + return std::make_unique (get_gasm ()); + } + + const gasm &get_gasm () const + { + return *as_a (&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 + clone () const final override + { + return std::make_unique (get_gassign ()); + } + + const gassign &get_gassign () const + { + return *as_a (&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 + clone () const final override + { + return std::make_unique (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 + clone () const final override + { + return std::make_unique (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 *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 (&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 + make (const gcall &call_stmt); + + std::unique_ptr + clone () const override + { + return std::make_unique (get_gcall ()); + } + + const gcall &get_gcall () const + { + return *as_a (&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 + clone () const final override + { + return std::make_unique (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 + clone () const final override + { + return std::make_unique (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 + clone () const final override + { + return std::make_unique (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 + clone () const final override + { + return std::make_unique (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 + clone () const final override + { + return std::make_unique (get_gresx ()); + } + + const gresx &get_gresx () const + { + return *as_a (&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 *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 *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 + clone () const final override + { + return std::make_unique (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 (&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 *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 + clone () const final override + { + return std::make_unique (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 (&get_ctrlflow_stmt ()); + } + +private: + bool + apply_constraints (const superedge *sedge, + region_model &model, + region_model_context *ctxt, + std::unique_ptr *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 + clone () const final override + { + return std::make_unique (*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 (&get_ctrlflow_stmt ()); + } + + private: + bool + apply_constraints (const superedge *sedge, + region_model &model, + region_model_context *ctxt, + std::unique_ptr *out) + const final override; + + std::vector 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 + make (supernode *src, + supernode *dest, + ::edge cfg_edge, + const geh_dispatch &geh_dispatch_stmt); + + const geh_dispatch & + get_geh_dispatch () const + { + return *as_a (&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 *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 *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 + clone () const final override + { + return std::make_unique (*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 *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 + clone () const final override + { + return std::make_unique (*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 *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 + maybe_make (::edge cfg_in_edge); + + std::unique_ptr + clone () const final override + { + return std::make_unique (*this); + } + + phis_for_edge_op (std::vector &&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 *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 &get_pairs () const { return m_pairs; } + +private: + static std::vector + 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 m_pairs; + ::edge m_cfg_in_edge; +}; + +} // namespace ana + +#endif /* GCC_ANALYZER_OPS_H */ diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc index 14d8f9f803f..e9830326390 100644 --- a/gcc/analyzer/pending-diagnostic.cc +++ b/gcc/analyzer/pending-diagnostic.cc @@ -214,19 +214,12 @@ pending_diagnostic::add_function_entry_event (const exploded_edge &eedge, 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 (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. diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index b5d90a22ae1..41449795560 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -325,16 +325,15 @@ class pending_diagnostic 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; } @@ -343,7 +342,8 @@ class pending_diagnostic 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. @@ -390,8 +390,7 @@ class pending_diagnostic /* 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; diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index 9baa0074b2b..d3fe751caac 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -69,171 +69,6 @@ point_kind_to_string (enum point_kind pk) } } -/* 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(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. */ @@ -253,25 +88,23 @@ public: } }; -/* 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 @@ -281,7 +114,17 @@ program_point::print (pretty_printer *pp, const format &f) const 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. */ @@ -294,10 +137,7 @@ program_point::dump () const } /* 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 @@ -305,39 +145,13 @@ program_point::to_json () const { auto point_obj = std::make_unique (); - 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 () @@ -352,7 +166,7 @@ hashval_t 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 (); } @@ -364,7 +178,7 @@ program_point::get_function_at_depth (unsigned depth) const { gcc_assert (depth <= m_call_string->length ()); if (depth == m_call_string->length ()) - return m_function_point.get_function (); + return m_snode->get_function (); else return get_call_string ()[depth].get_caller_function (); } @@ -388,294 +202,12 @@ program_point::validate () const == 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 (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 (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 (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::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 ()); } @@ -684,39 +216,10 @@ program_point::from_function_entry (const region_model_manager &mgr, 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. */ @@ -762,54 +265,6 @@ program_point::effectively_intraprocedural_p (const program_point &point_a, 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 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 @@ -821,14 +276,10 @@ test_program_point_equality () 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. */ @@ -836,8 +287,6 @@ test_program_point_equality () void analyzer_program_point_cc_tests () { - test_function_point_equality (); - test_function_point_ordering (); test_program_point_equality (); } diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h index fc543c01a73..8deef56ff8e 100644 --- a/gcc/analyzer/program-point.h +++ b/gcc/analyzer/program-point.h @@ -23,6 +23,7 @@ along with GCC; see the file COPYING3. If not see #include "pretty-print.h" #include "analyzer/call-string.h" +#include "analyzer/supergraph.h" namespace ana { @@ -63,107 +64,6 @@ 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. @@ -174,22 +74,23 @@ public: 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 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 @@ -199,42 +100,25 @@ public: /* 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. @@ -243,73 +127,33 @@ public: 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; }; diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 7962b452f8b..f803adbe589 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1263,138 +1263,6 @@ program_state::get_current_function () const 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) 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 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 @@ -1434,7 +1302,7 @@ program_state::prune_for_point (exploded_graph &eg, 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 @@ -1472,7 +1340,7 @@ program_state::prune_for_point (exploded_graph &eg, || 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, @@ -1507,8 +1375,8 @@ program_state::prune_for_point (exploded_graph &eg, 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); } } @@ -1804,14 +1672,14 @@ test_sm_state_map () std::vector> 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); @@ -1840,7 +1708,6 @@ test_sm_state_map () /* 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); @@ -1864,7 +1731,6 @@ test_sm_state_map () /* 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); @@ -1899,7 +1765,6 @@ test_sm_state_map () 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); @@ -1933,16 +1798,16 @@ test_program_state_1 () 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]; @@ -1963,7 +1828,8 @@ test_program_state_2 () tree string_cst_ptr = build_string_literal (4, "foo"); std::vector> checkers; - engine eng; + region_model_manager mgr; + engine eng (mgr); extrinsic_state ext_state (std::move (checkers), &eng); program_state s (ext_state); @@ -1983,9 +1849,9 @@ test_program_state_merging () 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); @@ -1995,10 +1861,10 @@ test_program_state_merging () 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]; @@ -2050,9 +1916,9 @@ test_program_state_merging () 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); diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 4278237adc8..97e696d4437 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -264,22 +264,6 @@ public: 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, diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 872b1d6a852..2a6d047f4ce 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -496,25 +496,25 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op, } /* 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; } @@ -1318,22 +1318,22 @@ region_model_manager::get_or_create_unmergeable (const svalue *arg) } /* 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); diff --git a/gcc/analyzer/region-model-manager.h b/gcc/analyzer/region-model-manager.h index 865bedfb862..a2c59c4157a 100644 --- a/gcc/analyzer/region-model-manager.h +++ b/gcc/analyzer/region-model-manager.h @@ -73,7 +73,7 @@ public: 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, diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index ceb5064007c..3ef5291d7b0 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -881,21 +881,12 @@ public: 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 (); @@ -2139,7 +2130,8 @@ public: 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 (); @@ -2242,6 +2234,46 @@ region_model::check_for_throw_inside_call (const gcall &call, 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 +{ +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. @@ -2287,6 +2319,20 @@ region_model::on_call_pre (const gcall &call, region_model_context *ctxt) 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 (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. */ @@ -2796,7 +2842,9 @@ region_model::on_return (const greturn *return_stmt, region_model_context *ctxt) 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); @@ -2807,7 +2855,7 @@ region_model::on_setjmp (const gcall &call, const exploded_node *enode, 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); @@ -2876,38 +2924,6 @@ region_model::on_longjmp (const gcall &longjmp_call, const gcall &setjmp_call, } } -/* 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 &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, @@ -6121,130 +6137,6 @@ region_model::get_representative_path_var (const region *reg, 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 svals_changing_meaning; - - for (gphi_iterator gpi = const_cast(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 *out) -{ - /* Handle frame updates for interprocedural edges. */ - switch (edge.m_kind) - { - default: - break; - - case SUPEREDGE_CALL: - { - const call_superedge *call_edge = as_a (&edge); - update_for_call_superedge (*call_edge, ctxt); - } - break; - - case SUPEREDGE_RETURN: - { - const return_superedge *return_edge - = as_a (&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 (last_stmt)) - { - const cfg_superedge *cfg_sedge = as_a (&edge); - return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt, out); - } - - if (const gswitch *switch_stmt = dyn_cast (last_stmt)) - { - const switch_cfg_superedge *switch_sedge - = as_a (&edge); - return apply_constraints_for_gswitch (*switch_sedge, switch_stmt, - ctxt, out); - } - - if (const geh_dispatch *eh_dispatch_stmt - = dyn_cast (last_stmt)) - { - const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge - = as_a (&edge); - return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge, - eh_dispatch_stmt, - ctxt, out); - } - - if (const ggoto *goto_stmt = dyn_cast (last_stmt)) - { - const cfg_superedge *cfg_sedge = as_a (&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 @@ -6291,28 +6183,6 @@ region_model::update_for_return_gcall (const gcall &call_stmt, 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. */ @@ -6346,376 +6216,6 @@ region_model::replay_call_summary (call_summary_replay &r, 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 *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 *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 (index_sval); - if (unaryop->get_op () == NOP_EXPR - && is_a (unaryop->get_arg ())) - if (const initial_svalue *initvalop = (as_a - (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 (*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 - (*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 *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 *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 (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 (*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 (*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 *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 (*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 (*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 (*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" @@ -6896,46 +6396,21 @@ public: m_call_stmt (call_stmt), m_caller_frame (caller_frame) {} - bool warn (std::unique_ptr 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 clone () const override - { - return std::make_unique (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; @@ -7397,9 +6872,9 @@ region_model::get_or_create_region_for_heap_alloc (const svalue *size_in_bytes, 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; @@ -7956,6 +7431,18 @@ region_model::set_errno (const call_details &cd) set_value (errno_reg, new_errno_sval, cd.get_ctxt ()); } +// class region_model_context + +bool +region_model_context:: +warn (std::unique_ptr d, + std::unique_ptr 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 @@ -8106,8 +7593,11 @@ rejected_ranges_constraint::dump_to_pp (pretty_printer *pp) const /* 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) { } @@ -9473,18 +8963,24 @@ test_state_merging () 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. */ @@ -9685,7 +9181,7 @@ static void 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; @@ -9694,7 +9190,7 @@ test_widening_constraints () 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 (); diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 7f33a4572e7..091f0d0ac4c 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #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" @@ -365,25 +366,13 @@ class region_model 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 &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 *out); - void update_for_gcall (const gcall &call_stmt, region_model_context *ctxt, function *callee = nullptr); @@ -656,20 +645,6 @@ class region_model 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 *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 *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; @@ -693,28 +668,6 @@ private: 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 *out); - bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge, - const gswitch *switch_stmt, - region_model_context *ctxt, - std::unique_ptr *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 *out); - void poison_any_pointers_to_descendents (const region *reg, enum poison_kind pkind); @@ -814,11 +767,19 @@ private: class region_model_context { public: + bool + warn (std::unique_ptr d, + std::unique_ptr 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 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 d, + pending_location &&ploc) = 0; /* Hook for clients to add a note to the last previously stored pending diagnostic. */ @@ -943,8 +904,17 @@ class region_model_context class noop_region_model_context : public region_model_context { public: - bool warn (std::unique_ptr, - 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_location &&) override + { + return false; + } void add_note (std::unique_ptr) override; void add_event (std::unique_ptr) override; void on_svalue_leak (const svalue *) override {} @@ -1026,11 +996,21 @@ private: class region_model_context_decorator : public region_model_context { public: - bool warn (std::unique_ptr 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 d, + pending_location &&ploc) override + { + if (m_inner) + return m_inner->warn_at (std::move (d), std::move (ploc)); else return false; } @@ -1214,11 +1194,12 @@ protected: class annotating_context : public region_model_context_decorator { public: - bool warn (std::unique_ptr d, - const stmt_finder *custom_finder) override + bool + warn_at (std::unique_ptr 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; @@ -1266,9 +1247,10 @@ struct model_merger } 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); @@ -1351,7 +1333,9 @@ private: 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 () @@ -1362,8 +1346,8 @@ public: void log_stats (logger *logger) const; private: + region_model_manager &m_mgr; const supergraph *m_sg; - region_model_manager m_mgr; }; } // namespace ana @@ -1384,8 +1368,9 @@ using namespace ::selftest; class test_region_model_context : public noop_region_model_context { public: - bool warn (std::unique_ptr d, - const stmt_finder *) final override + bool + warn_at (std::unique_ptr d, + pending_location &&) final override { m_diagnostics.safe_push (d.release ()); return true; diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 8f294b53398..bcbb34873ea 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -1393,9 +1393,12 @@ frame_region::get_region_for_local (region_model_manager *mgr, = 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; diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc index b8ae0a1747e..f694223bee9 100644 --- a/gcc/analyzer/sm-fd.cc +++ b/gcc/analyzer/sm-fd.cc @@ -108,11 +108,11 @@ public: 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; @@ -222,36 +222,34 @@ public: 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; @@ -259,14 +257,12 @@ private: 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; }; @@ -1328,7 +1324,7 @@ fd_state_machine::mark_as_valid_fd (region_model *model, } 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 (stmt)) @@ -1336,66 +1332,66 @@ fd_state_machine::on_stmt (sm_context &sm_ctxt, const supernode *node, { 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); } } @@ -1405,7 +1401,7 @@ fd_state_machine::on_stmt (sm_context &sm_ctxt, const supernode *node, 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 { @@ -1443,7 +1439,7 @@ fd_state_machine::check_for_fd_attrs ( { 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; @@ -1456,7 +1452,7 @@ fd_state_machine::check_for_fd_attrs ( if (is_closed_fd_p (state)) { - sm_ctxt.warn (node, stmt, arg, + sm_ctxt.warn (arg, std::make_unique (*this, diag_arg, fndecl, attr_name, @@ -1468,7 +1464,7 @@ fd_state_machine::check_for_fd_attrs ( { if (!is_constant_fd_p (state)) { - sm_ctxt.warn (node, stmt, arg, + sm_ctxt.warn (arg, std::make_unique (*this, diag_arg, fndecl, attr_name, @@ -1486,7 +1482,7 @@ fd_state_machine::check_for_fd_attrs ( if (is_writeonly_fd_p (state)) { sm_ctxt.warn - (node, stmt, arg, + (arg, std::make_unique (*this, diag_arg, DIRS_WRITE, fndecl, @@ -1500,7 +1496,7 @@ fd_state_machine::check_for_fd_attrs ( if (is_readonly_fd_p (state)) { sm_ctxt.warn - (node, stmt, arg, + (arg, std::make_unique (*this, diag_arg, DIRS_READ, fndecl, @@ -1516,8 +1512,7 @@ fd_state_machine::check_for_fd_attrs ( 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) @@ -1532,52 +1527,49 @@ fd_state_machine::on_open (sm_context &sm_ctxt, const supernode *node, 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 (*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 (*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) @@ -1586,9 +1578,9 @@ fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node, 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; @@ -1596,7 +1588,7 @@ fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node, 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; @@ -1604,10 +1596,10 @@ fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node, 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 (*this, diag_arg_2, - callee_fndecl)); + sm_ctxt.warn + (arg_2, + std::make_unique (*this, diag_arg_2, + callee_fndecl)); return; } /* dup2 returns value of its second argument on success.But, the @@ -1616,9 +1608,9 @@ fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node, 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)); } @@ -1627,65 +1619,62 @@ fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node, } 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 (*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 (*this, diag_arg, callee_fndecl)); } @@ -1697,7 +1686,7 @@ fd_state_machine::check_for_open_fd ( || state == m_listening_stream_socket) /* Complain about fncall on socket in wrong phase. */ sm_ctxt.warn - (node, stmt, arg, + (arg, std::make_unique (*this, diag_arg, callee_fndecl, state, @@ -1710,10 +1699,10 @@ fd_state_machine::check_for_open_fd ( || state == m_stop)) { if (!is_constant_fd_p (state)) - sm_ctxt.warn ( - node, stmt, arg, - std::make_unique (*this, diag_arg, - callee_fndecl)); + sm_ctxt.warn + (arg, + std::make_unique (*this, diag_arg, + callee_fndecl)); } switch (callee_fndecl_dir) { @@ -1723,7 +1712,7 @@ fd_state_machine::check_for_open_fd ( 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 (*this, diag_arg, DIRS_WRITE, callee_fndecl)); } @@ -1734,7 +1723,7 @@ fd_state_machine::check_for_open_fd ( 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 (*this, diag_arg, DIRS_READ, callee_fndecl)); } @@ -1781,12 +1770,9 @@ bool 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) @@ -1806,11 +1792,11 @@ fd_state_machine::on_socket (const call_details &cd, 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 (*this, NULL_TREE, nullptr)); } else @@ -1837,17 +1823,14 @@ fd_state_machine::check_for_socket_fd (const call_details &cd, 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 (*this, diag_arg, cd.get_fndecl_for_call ())); if (complained) @@ -1860,7 +1843,7 @@ fd_state_machine::check_for_socket_fd (const call_details &cd, /* 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 (*this, diag_arg, cd.get_fndecl_for_call (), old_state, @@ -1874,7 +1857,7 @@ fd_state_machine::check_for_socket_fd (const call_details &cd, { tree diag_arg = sm_ctxt.get_diagnostic_tree (fd_sval); sm_ctxt.warn - (node, &call, fd_sval, + (fd_sval, std::make_unique (*this, diag_arg, cd.get_fndecl_for_call ())); if (complained) @@ -1900,7 +1883,6 @@ fd_state_machine::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 @@ -1924,7 +1906,7 @@ fd_state_machine::check_for_new_socket_fd (const call_details &cd, 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 @@ -1937,7 +1919,7 @@ fd_state_machine::check_for_new_socket_fd (const call_details &cd, /* 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 (*this, diag_arg, cd.get_fndecl_for_call (), old_state, @@ -1949,8 +1931,7 @@ fd_state_machine::check_for_new_socket_fd (const call_details &cd, { /* 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. */ @@ -1969,18 +1950,14 @@ bool 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; @@ -2000,7 +1977,7 @@ fd_state_machine::on_bind (const call_details &cd, 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 @@ -2021,18 +1998,14 @@ bool 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 @@ -2047,14 +2020,14 @@ fd_state_machine::on_listen (const call_details &cd, 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 (*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 (*this, diag_arg, cd.get_fndecl_for_call (), old_state, @@ -2066,8 +2039,7 @@ fd_state_machine::on_listen (const call_details &cd, 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 { @@ -2075,8 +2047,7 @@ fd_state_machine::on_listen (const call_details &cd, 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; @@ -2090,17 +2061,14 @@ bool 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 ()) { @@ -2150,14 +2118,13 @@ fd_state_machine::on_accept (const call_details &cd, } /* 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. */ @@ -2168,14 +2135,14 @@ fd_state_machine::on_accept (const call_details &cd, 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 (*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 (*this, diag_arg, cd.get_fndecl_for_call (), old_state, @@ -2198,12 +2165,11 @@ fd_state_machine::on_accept (const call_details &cd, 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 (*this, NULL_TREE, nullptr)); } else @@ -2224,18 +2190,14 @@ bool 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; @@ -2258,7 +2220,7 @@ fd_state_machine::on_connect (const call_details &cd, 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 { @@ -2274,9 +2236,10 @@ fd_state_machine::on_connect (const call_details &cd, } 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 ()) { @@ -2286,11 +2249,10 @@ fd_state_machine::on_condition (sm_context &sm_ctxt, const supernode *node, 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); } } } @@ -2298,34 +2260,32 @@ fd_state_machine::on_condition (sm_context &sm_ctxt, const supernode *node, 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 @@ -2715,8 +2675,7 @@ class kf_isatty : public known_function 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) diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index c852c02804b..eab8025d579 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -62,12 +62,9 @@ public: } 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; @@ -408,7 +405,6 @@ is_file_using_fn_p (tree fndecl) bool fileptr_state_machine::on_stmt (sm_context &sm_ctxt, - const supernode *node, const gimple *stmt) const { if (const gcall *call = dyn_cast (stmt)) @@ -418,7 +414,7 @@ fileptr_state_machine::on_stmt (sm_context &sm_ctxt, { 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. */ @@ -430,21 +426,21 @@ fileptr_state_machine::on_stmt (sm_context &sm_ctxt, { 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 (*this, diag_arg)); - sm_ctxt.set_next_state (stmt, arg, m_stop); + sm_ctxt.set_next_state (arg, m_stop); } return true; } @@ -466,8 +462,6 @@ fileptr_state_machine::on_stmt (sm_context &sm_ctxt, 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 @@ -485,14 +479,12 @@ fileptr_state_machine::on_condition (sm_context &sm_ctxt, 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); } } @@ -687,12 +679,17 @@ register_known_file_functions (known_function_manager &kfm) kfm.add (BUILT_IN_VPRINTF, std::make_unique ()); kfm.add ("ferror", std::make_unique ()); + kfm.add ("ferror_unlocked", std::make_unique ()); kfm.add ("fgets", std::make_unique ()); kfm.add ("fgets_unlocked", std::make_unique ()); // non-standard kfm.add ("fileno", std::make_unique ()); + kfm.add ("fileno_unlocked", std::make_unique ()); kfm.add ("fread", std::make_unique ()); + kfm.add ("fread_unlocked", std::make_unique ()); kfm.add ("getc", std::make_unique ()); + kfm.add ("getc_unlocked", std::make_unique ()); kfm.add ("getchar", std::make_unique ()); + kfm.add ("getchar_unlocked", std::make_unique ()); /* Some C++ implementations use the std:: copies of these functions from for , so we must match against these too. */ diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 88bfc74888c..7fa50fd7a56 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -386,17 +386,17 @@ public: } 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; @@ -421,8 +421,7 @@ public: 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, @@ -476,8 +475,6 @@ private: 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; @@ -486,24 +483,18 @@ private: 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; @@ -1782,13 +1773,9 @@ private: 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; } @@ -2043,8 +2030,7 @@ known_allocator_p (const_tree fndecl, const gcall &call) 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) @@ -2061,7 +2047,7 @@ malloc_state_machine::maybe_assume_non_null (sm_context &sm_ctxt, 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); } } @@ -2071,13 +2057,11 @@ malloc_state_machine::maybe_assume_non_null (sm_context &sm_ctxt, 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. */ @@ -2085,29 +2069,28 @@ malloc_state_machine::handle_nonnull (sm_context &sm_ctxt, { tree diag_arg = sm_ctxt.get_diagnostic_tree (arg); sm_ctxt.warn - (node, stmt, arg, + (arg, std::make_unique (*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 (*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 (stmt)) @@ -2136,13 +2119,13 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, 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; } @@ -2152,7 +2135,7 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, { 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; } @@ -2160,7 +2143,7 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, || 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; } @@ -2169,7 +2152,7 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, || 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; } @@ -2204,62 +2187,6 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, 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); @@ -2267,7 +2194,7 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, { 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); } } } @@ -2291,11 +2218,10 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, && 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); } @@ -2304,7 +2230,7 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, 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++) @@ -2319,33 +2245,33 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, { 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 (*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 (*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 (*this, diag_arg, astate->m_deallocator)); - sm_ctxt.set_next_state (stmt, arg, m_stop); + sm_ctxt.set_next_state (arg, m_stop); } } } @@ -2358,8 +2284,6 @@ malloc_state_machine::on_stmt (sm_context &sm_ctxt, 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 { @@ -2392,11 +2316,11 @@ maybe_complain_about_deref_before_check (sm_context &sm_ctxt, 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; } @@ -2404,9 +2328,9 @@ maybe_complain_about_deref_before_check (sm_context &sm_ctxt, tree diag_ptr = sm_ctxt.get_diagnostic_tree (ptr); if (diag_ptr) sm_ctxt.warn - (node, stmt, ptr, + (ptr, std::make_unique (*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. @@ -2422,8 +2346,8 @@ malloc_state_machine::on_allocator_call (sm_context &sm_ctxt, 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)); @@ -2439,8 +2363,7 @@ malloc_state_machine::on_allocator_call (sm_context &sm_ctxt, 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 { @@ -2452,15 +2375,14 @@ malloc_state_machine::handle_free_of_non_heap (sm_context &sm_ctxt, 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 (*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 @@ -2469,11 +2391,11 @@ malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt, 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); @@ -2482,13 +2404,13 @@ malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt, { /* Wrong allocator. */ tree diag_arg = sm_ctxt.get_diagnostic_tree (arg); - sm_ctxt.warn (node, &call, arg, + sm_ctxt.warn (arg, std::make_unique (*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"; @@ -2497,14 +2419,14 @@ malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt, { /* 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 (*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); } } @@ -2518,7 +2440,6 @@ malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt, void malloc_state_machine::on_realloc_call (sm_context &sm_ctxt, - const supernode *node, const gcall &call) const { const unsigned argno = 0; @@ -2526,7 +2447,7 @@ malloc_state_machine::on_realloc_call (sm_context &sm_ctxt, 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)) { @@ -2536,11 +2457,11 @@ malloc_state_machine::on_realloc_call (sm_context &sm_ctxt, { /* Wrong allocator. */ tree diag_arg = sm_ctxt.get_diagnostic_tree (arg); - sm_ctxt.warn (node, &call, arg, + sm_ctxt.warn (arg, std::make_unique (*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 (); } @@ -2549,16 +2470,16 @@ malloc_state_machine::on_realloc_call (sm_context &sm_ctxt, { /* 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 (*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 (); } @@ -2568,15 +2489,79 @@ malloc_state_machine::on_realloc_call (sm_context &sm_ctxt, 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. @@ -2584,8 +2569,6 @@ malloc_state_machine::on_phi (sm_context &sm_ctxt, 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 @@ -2601,19 +2584,19 @@ malloc_state_machine::on_condition (sm_context &sm_ctxt, 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); } } @@ -2724,16 +2707,15 @@ malloc_state_machine::unaffected_by_call_p (tree fndecl) 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 diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc index 12449a1d48d..b87a40c0332 100644 --- a/gcc/analyzer/sm-pattern-test.cc +++ b/gcc/analyzer/sm-pattern-test.cc @@ -50,12 +50,9 @@ public: 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; @@ -102,7 +99,6 @@ pattern_test_state_machine::pattern_test_state_machine (logger *logger) 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; @@ -116,22 +112,17 @@ pattern_test_state_machine::on_stmt (sm_context &sm_ctxt ATTRIBUTE_UNUSED, 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 (lhs_expr, op, rhs_cst)); } } diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc index 4611b10e35a..08620481487 100644 --- a/gcc/analyzer/sm-sensitive.cc +++ b/gcc/analyzer/sm-sensitive.cc @@ -44,7 +44,6 @@ public: 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; @@ -57,8 +56,6 @@ public: private: void warn_for_any_exposure (sm_context &sm_ctxt, - const supernode *node, - const gimple *stmt, tree arg) const; }; @@ -180,14 +177,12 @@ sensitive_state_machine::sensitive_state_machine (logger *logger) 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 (*this, diag_arg)); } @@ -198,7 +193,6 @@ sensitive_state_machine::warn_for_any_exposure (sm_context &sm_ctxt, bool sensitive_state_machine::on_stmt (sm_context &sm_ctxt, - const supernode *node, const gimple *stmt) const { if (const gcall *call = dyn_cast (stmt)) @@ -208,7 +202,7 @@ sensitive_state_machine::on_stmt (sm_context &sm_ctxt, { 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") @@ -218,14 +212,14 @@ sensitive_state_machine::on_stmt (sm_context &sm_ctxt, 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. diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc index 35ecde1d24b..6bd00ab0f64 100644 --- a/gcc/analyzer/sm-signal.cc +++ b/gcc/analyzer/sm-signal.cc @@ -63,7 +63,6 @@ public: 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; @@ -221,7 +220,8 @@ public: } 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 @@ -258,7 +258,6 @@ public: = 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); @@ -324,7 +323,6 @@ signal_unsafe_p (tree fndecl) 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 (); @@ -351,7 +349,7 @@ signal_state_machine::on_stmt (sm_context &sm_ctxt, 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 (*this, *call, callee_fndecl)); } diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc index f2a94e8a4f6..42057e08c60 100644 --- a/gcc/analyzer/sm-taint.cc +++ b/gcc/analyzer/sm-taint.cc @@ -102,18 +102,13 @@ public: } 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; @@ -125,15 +120,12 @@ public: 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: @@ -1089,7 +1081,6 @@ is_assertion_failure_handler_p (tree fndecl) bool taint_state_machine::on_stmt (sm_context &sm_ctxt, - const supernode *node, const gimple *stmt) const { if (const gcall *call = dyn_cast (stmt)) @@ -1099,24 +1090,24 @@ taint_state_machine::on_stmt (sm_context &sm_ctxt, { 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 (*this, NULL_TREE, callee_fndecl)); } @@ -1141,7 +1132,7 @@ taint_state_machine::on_stmt (sm_context &sm_ctxt, 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; } } @@ -1152,8 +1143,8 @@ taint_state_machine::on_stmt (sm_context &sm_ctxt, 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 (stmt)) @@ -1162,7 +1153,7 @@ taint_state_machine::on_stmt (sm_context &sm_ctxt, 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_)); } @@ -1175,12 +1166,11 @@ taint_state_machine::on_stmt (sm_context &sm_ctxt, 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); @@ -1192,15 +1182,10 @@ taint_state_machine::check_control_flow_arg_for_taint (sm_context &sm_ctxt, 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) { @@ -1234,10 +1219,10 @@ taint_state_machine::on_condition (sm_context &sm_ctxt, /* (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: @@ -1275,12 +1260,11 @@ taint_state_machine::on_condition (sm_context &sm_ctxt, 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; } } @@ -1288,10 +1272,10 @@ taint_state_machine::on_condition (sm_context &sm_ctxt, /* (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: @@ -1306,8 +1290,6 @@ taint_state_machine::on_condition (sm_context &sm_ctxt, void taint_state_machine::on_bounded_ranges (sm_context &sm_ctxt, - const supernode *, - const gimple *stmt, const svalue &sval, const bounded_ranges &ranges) const { @@ -1335,20 +1317,20 @@ taint_state_machine::on_bounded_ranges (sm_context &sm_ctxt, /* 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 @@ -1426,7 +1408,6 @@ taint_state_machine::combine_states (state_t s0, state_t s1) const void taint_state_machine::check_for_tainted_size_arg (sm_context &sm_ctxt, - const supernode *node, const gcall &call, tree callee_fndecl) const { @@ -1460,14 +1441,14 @@ taint_state_machine::check_for_tainted_size_arg (sm_context &sm_ctxt, 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 (*this, diag_size, b, callee_fndecl, @@ -1482,7 +1463,6 @@ taint_state_machine::check_for_tainted_size_arg (sm_context &sm_ctxt, 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 (); @@ -1498,7 +1478,7 @@ taint_state_machine::check_for_tainted_divisor (sm_context &sm_ctxt, 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)) { @@ -1513,9 +1493,9 @@ taint_state_machine::check_for_tainted_divisor (sm_context &sm_ctxt, tree diag_divisor = sm_ctxt.get_diagnostic_tree (divisor_expr); sm_ctxt.warn - (node, assign, divisor_expr, + (divisor_expr, std::make_unique (*this, diag_divisor, b)); - sm_ctxt.set_next_state (assign, divisor_sval, m_stop); + sm_ctxt.set_next_state (divisor_sval, m_stop); } } diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc index c93e9c2b67b..640c197c6e7 100644 --- a/gcc/analyzer/sm.cc +++ b/gcc/analyzer/sm.cc @@ -161,16 +161,16 @@ state_machine::to_json () const } 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 } diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index 4633facac3a..4c3009228c4 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -21,6 +21,8 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_ANALYZER_SM_H #define GCC_ANALYZER_SM_H +#include "analyzer/analyzer-logging.h" + /* Utility functions for use by state machines. */ namespace ana { @@ -96,19 +98,21 @@ public: /* 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 @@ -117,8 +121,6 @@ public: 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 { @@ -236,7 +238,7 @@ public: }; /* 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 { @@ -249,58 +251,47 @@ public: 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 d) = 0; - virtual void warn (const supernode *node, const gimple *stmt, - const svalue *var, + virtual void warn (const svalue *var, std::unique_ptr d) = 0; /* For use when generating trees when creating pending_diagnostics, so that @@ -341,6 +332,9 @@ public: 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) {} diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index 1371db904aa..be878036f08 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ +#define INCLUDE_SET #include "analyzer/common.h" #include "timevar.h" @@ -75,19 +76,20 @@ get_candidate_for_purging (tree node) } /* 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) { @@ -143,29 +145,24 @@ private: 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 @@ -224,23 +221,19 @@ state_purge_map::state_purge_map (const supergraph &sg, } /* 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); } } @@ -278,6 +271,16 @@ state_purge_map::get_or_create_data_for_decl (const function &fun, tree decl) 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. @@ -292,7 +295,7 @@ state_purge_map::get_or_create_data_for_decl (const function &fun, tree decl) 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 ()); @@ -307,7 +310,7 @@ state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map, map.log ("def stmt: %s", pp_formatted_text (&pp)); } - auto_vec worklist; + auto_vec worklist; /* Add all immediate uses of name to the worklist. Compare with debug_immediate_uses. */ @@ -334,62 +337,43 @@ state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map, 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 (snode)->start_phis (); - !gsi_end_p (gpi); gsi_next (&gpi)) + const gphi *phi = as_a (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); } } } @@ -399,275 +383,100 @@ state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map, 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 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 indices; + auto_vec 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 (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 (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 *worklist, +state_purge_per_ssa_name::add_to_worklist (const supernode &snode, + auto_vec *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 (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 *worklist, - const state_purge_map &map) +state_purge_per_ssa_name::process_supernode (const supernode &snode, + auto_vec *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 (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. */ @@ -682,34 +491,34 @@ state_purge_per_decl::state_purge_per_decl (const state_purge_map &map, 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, @@ -722,11 +531,11 @@ state_purge_per_decl::process_worklists (const state_purge_map &map, /* Worklist for walking backwards from uses. */ { - auto_vec worklist; + auto_vec 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); @@ -738,25 +547,25 @@ state_purge_per_decl::process_worklists (const state_purge_map &map, 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 worklist; + auto_vec 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. */ @@ -764,37 +573,29 @@ state_purge_per_decl::process_worklists (const state_purge_map &map, 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 *worklist, +state_purge_per_decl::add_to_worklist (const supernode &snode, + auto_vec *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); @@ -803,9 +604,9 @@ state_purge_per_decl::add_to_worklist (const function_point &point, { 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); } } @@ -850,217 +651,96 @@ fully_overwrites_p (const gimple *stmt, tree decl, 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 *worklist, - point_set_t *seen, - const state_purge_map &map, - const region_model &model) +process_supernode_backwards (const supernode &snode, + auto_vec *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 *worklist, - point_set_t *seen, - const state_purge_map &map) +process_supernode_forwards (const supernode &snode, + auto_vec *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 (m_points_needing_decl).contains (point); + return const_cast (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 @@ -1069,61 +749,19 @@ state_purge_per_decl::needed_at_point_p (const function_point &point) const 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 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 +/* Print V to GV as a comma-separated list in braces within a , + 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 &v, bool within_table) + const auto_vec &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) { @@ -1132,54 +770,18 @@ print_vec_of_names (graphviz_out *gv, const char *title, 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 elements. */ - -void -state_purge_annotator::print_needed (graphviz_out *gv, - const function_point &point, - bool within_table) const -{ auto_vec needed; auto_vec not_needed; for (state_purge_map::ssa_iterator iter = m_map->begin_ssas (); @@ -1188,9 +790,9 @@ state_purge_annotator::print_needed (graphviz_out *gv, { 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); @@ -1202,17 +804,17 @@ state_purge_annotator::print_needed (graphviz_out *gv, { 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 */ diff --git a/gcc/analyzer/state-purge.h b/gcc/analyzer/state-purge.h index 2bac564175c..2da8fc645f4 100644 --- a/gcc/analyzer/state-purge.h +++ b/gcc/analyzer/state-purge.h @@ -21,53 +21,6 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_ANALYZER_STATE_PURGE_H #define GCC_ANALYZER_STATE_PURGE_H -/* Hash traits for function_point. */ - -template <> struct default_hash_traits -: public pod_hash_traits -{ - static const bool empty_zero_p = false; -}; - -template <> -inline hashval_t -pod_hash_traits::hash (value_type v) -{ - return v.hash (); -} - -template <> -inline bool -pod_hash_traits::equal (const value_type &existing, - const value_type &candidate) -{ - return existing == candidate; -} -template <> -inline void -pod_hash_traits::mark_deleted (value_type &v) -{ - v = function_point::deleted (); -} -template <> -inline void -pod_hash_traits::mark_empty (value_type &v) -{ - v = function_point::empty (); -} -template <> -inline bool -pod_hash_traits::is_deleted (value_type v) -{ - return v.get_kind () == PK_DELETED; -} -template <> -inline bool -pod_hash_traits::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 @@ -123,6 +76,10 @@ public: 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); @@ -140,7 +97,7 @@ public: tree get_fndecl () const { return m_fun.decl; } protected: - typedef hash_set point_set_t; + typedef hash_set point_set_t; state_purge_per_tree (const function &fun) : m_fun (fun) @@ -165,21 +122,22 @@ public: 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 *worklist, +private: + void add_to_worklist (const supernode &node, + auto_vec *worklist, logger *logger); - void process_point (const function_point &point, - auto_vec *worklist, - const state_purge_map &map); + void process_supernode (const supernode &node, + auto_vec *worklist, + const state_purge_map &map); - point_set_t m_points_needing_name; + point_set_t m_snodes_needing_name; tree m_name; }; @@ -197,34 +155,38 @@ public: 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 *worklist, + void add_to_worklist (const supernode &node, + auto_vec *worklist, point_set_t *seen, logger *logger); - void process_point_backwards (const function_point &point, - auto_vec *worklist, - point_set_t *seen, - const state_purge_map &map, - const region_model &model); - void process_point_forwards (const function_point &point, - auto_vec *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 *worklist, + point_set_t *seen, + const state_purge_map &map, + const region_model &model); + void process_supernode_forwards (const supernode &snode, + auto_vec *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; }; @@ -236,18 +198,11 @@ class state_purge_annotator : public dot_annotator 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; }; diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index b354f9b6387..49661f4904c 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -3578,6 +3578,26 @@ store::canonicalize (store_manager *mgr) } } +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: @@ -3618,7 +3638,7 @@ store::loop_replay_fixup (const store *other_store, { 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 (), diff --git a/gcc/analyzer/supergraph-fixup-locations.cc b/gcc/analyzer/supergraph-fixup-locations.cc new file mode 100644 index 00000000000..41544d756d2 --- /dev/null +++ b/gcc/analyzer/supergraph-fixup-locations.cc @@ -0,0 +1,123 @@ +/* Fixing up location_t values of supernodes. + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 */ diff --git a/gcc/analyzer/supergraph-manipulation.h b/gcc/analyzer/supergraph-manipulation.h new file mode 100644 index 00000000000..35592027097 --- /dev/null +++ b/gcc/analyzer/supergraph-manipulation.h @@ -0,0 +1,73 @@ +/* Classes for manipulating the supergraph. + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 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 */ diff --git a/gcc/analyzer/supergraph-simplify.cc b/gcc/analyzer/supergraph-simplify.cc new file mode 100644 index 00000000000..2dff3bf405a --- /dev/null +++ b/gcc/analyzer/supergraph-simplify.cc @@ -0,0 +1,317 @@ +/* Simplifying the supergraph. + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 m_nodes_to_remove; + std::set 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 */ diff --git a/gcc/analyzer/supergraph-sorting.cc b/gcc/analyzer/supergraph-sorting.cc new file mode 100644 index 00000000000..8021b152992 --- /dev/null +++ b/gcc/analyzer/supergraph-sorting.cc @@ -0,0 +1,266 @@ +/* Sorting the nodes in the supergraph. + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 &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 +static bool +set_contains_p (const std::set &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 m_set_of_ordered_nodes; + std::set m_set_of_queued_nodes; + typedef fibonacci_heap 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 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 +get_node_ordering (const supergraph &sg, + logger *logger) +{ + LOG_SCOPE (logger); + + std::vector 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 ordering = get_node_ordering (*this, logger); + reorder_nodes_and_ids (ordering, logger); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 8592db78395..f75fab1356d 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ +#define INCLUDE_DEQUE #include "analyzer/common.h" #include "timevar.h" @@ -28,13 +29,15 @@ along with GCC; see the file COPYING3. If not see #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 @@ -52,25 +55,6 @@ get_ultimate_function_for_cgraph_edge (cgraph_edge *edge) 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 (stmt); - if (!call) - return nullptr; - cgraph_edge *edge - = cgraph_node::get (fun->decl)->get_edge (const_cast (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 @@ -115,108 +99,93 @@ saved_uids::restore_uids () const 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 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 (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 (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 (); @@ -226,6 +195,10 @@ supergraph::supergraph (logger *logger) 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) @@ -234,80 +207,154 @@ supergraph::supergraph (logger *logger) 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(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 op; + switch (gimple_code (next_stmt)) + { + default: + gcc_unreachable (); + break; + case GIMPLE_ASM: + op = std::make_unique + (*as_a (next_stmt)); + break; + case GIMPLE_ASSIGN: + op = std::make_unique + (*as_a (next_stmt)); + break; + case GIMPLE_CALL: + op = call_and_return_op::make + (*as_a (next_stmt)); + break; + case GIMPLE_PREDICT: + op = std::make_unique (*next_stmt); + break; + case GIMPLE_RESX: + op = std::make_unique + (*as_a (next_stmt)); + break; + case GIMPLE_RETURN: + op = std::make_unique + (*as_a (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. */ @@ -347,6 +394,8 @@ supergraph::dump_dot_to_pp (pretty_printer *pp, { function *fun = node->get_fun (); gcc_assert (fun); + auto_cfun sentinel (fun); + const char *funcname = function_name (fun); gv.println ("subgraph \"cluster_%s\" {", funcname); @@ -357,45 +406,14 @@ supergraph::dump_dot_to_pp (pretty_printer *pp, " 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 (); @@ -409,11 +427,108 @@ supergraph::dump_dot_to_pp (pretty_printer *pp, 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 @@ -472,72 +587,185 @@ supergraph::to_json () const 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 &snodes_to_delete) { - /* Special-case switch and eh_dispatch edges. */ - gimple *stmt = src->get_last_stmt (); - std::unique_ptr new_edge; - if (stmt && stmt->code == GIMPLE_SWITCH) - new_edge = std::make_unique (src, dest, e); - else if (stmt && stmt->code == GIMPLE_EH_DISPATCH) - new_edge = eh_dispatch_cfg_superedge::make (src, dest, e, - as_a (stmt)); - else - new_edge = std::make_unique (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 ctrlflow_op; + if (ctrlflow_stmt) + switch (gimple_code (ctrlflow_stmt)) + { + default: + gcc_unreachable (); + break; + case GIMPLE_COND: + ctrlflow_op = std::make_unique + (cfg_edge, + *as_a (ctrlflow_stmt)); + break; + case GIMPLE_EH_DISPATCH: + ctrlflow_op + = eh_dispatch_edge_op::make (src, dest, + cfg_edge, + *as_a (ctrlflow_stmt)); + break; + case GIMPLE_GOTO: + { + ctrlflow_op = std::make_unique + (cfg_edge, + *as_a (ctrlflow_stmt), + dest->m_label); + } + break; + case GIMPLE_SWITCH: + ctrlflow_op = std::make_unique + (*src->get_function (), + cfg_edge, + *as_a (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 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 + /* Implementation of dnode::dump_dot vfunc for supernodes. Write a cluster for the node, and within it a .dot node showing @@ -547,144 +775,117 @@ supergraph::add_return_superedge (supernode *src, supernode *dest, 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, "
"); pp_write_text_to_stream (pp); - bool had_row = false; + pp_printf (pp, ""); + 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, ""); + pp_newline (pp); + } + if (m_state_merger_node) + { + pp_string (pp, ""); pp_newline (pp); - - had_row = true; } - if (entry_p ()) { pp_string (pp, ""); pp_newline (pp); - had_row = true; } - - if (return_p ()) + else if (exit_p ()) { pp_string (pp, ""); pp_newline (pp); - had_row = true; } - /* Phi nodes. */ - for (gphi_iterator gpi = const_cast (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, ""); + 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, "", m_loc); + pp_string (pp, ""); } - - /* 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, "", 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, "", + 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, ""); + } + } } - /* 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, ""); - pp_newline (pp); - } + pp_printf (pp, "", m_stmt_loc); pp_string (pp, "
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, "
(preserve)
" + "STATE MERGER
ENTRY
EXIT
UNKNOWN_LOCATION
location: 0x%lx
UNKNOWN_LOCATION
location: 0x%lx
%s:%i:
"); + pp_string (pp, ""); + + // Line number: + pp_printf (pp, (""), + exploc.line); + + // Line contents: + pp_string (pp, ""); + + pp_string (pp, "
" + " %i"); + 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, "
"); + pp_string (pp, "
(empty)
m_stmt_loc: 0x%lx
>];\n\n"); pp_flush (pp); - - /* Terminate "subgraph" */ - gv->outdent (); - gv->println ("}"); } /* Write an ID for this node to PP, for use in .dot output. */ @@ -692,184 +893,33 @@ supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const 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 supernode::to_json () const { auto snode_obj = std::make_unique (); - 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 (); - for (gphi_iterator gpi = const_cast (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 (); - 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 (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) { @@ -899,23 +949,6 @@ superedge::dump_dot (graphviz_out *gv, const dump_args_t &) const 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 ()) { @@ -950,60 +983,42 @@ superedge::dump_dot (graphviz_out *gv, const dump_args_t &) const 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 superedge::to_json () const { auto sedge_obj = std::make_unique (); - 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 @@ -1021,563 +1036,48 @@ superedge::get_description (bool user_facing) const 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::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 - (src_snode, dst_snode, - e, eh_dispatch_stmt, - eh_reg, ehc); - } - break; - case ERT_ALLOWED_EXCEPTIONS: - return std::make_unique - (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 *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 *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 diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index f64a2f4b5d7..43ac3fc01fa 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -24,11 +24,14 @@ along with GCC; see the file COPYING3. If not see #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 { @@ -38,29 +41,11 @@ 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 @@ -79,13 +64,16 @@ struct supergraph_traits 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; }; @@ -102,70 +90,56 @@ private: auto_vec > 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 { 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 (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 - (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 - (m_cgraph_edge_to_call_superedge).get (edge)); + return *const_cast (m_bb_to_final_node).get (bb); } - return_superedge *get_edge_for_return (cgraph_edge *edge) const - { - return (*const_cast - (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 - (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 - (m_cfg_edge_to_cfg_superedge).get (e)); - } - - supernode *get_supernode_for_stmt (const gimple *stmt) const - { - return (*const_cast (m_stmt_to_node_t).get - (const_cast (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; @@ -177,11 +151,6 @@ public: 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 @@ -189,48 +158,64 @@ public: 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 &snodes); - typedef ordered_hash_map 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_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_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_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 &ordering, + logger *logger); - typedef ordered_hash_map - cgraph_edge_to_intraproc_superedge_t; - cgraph_edge_to_intraproc_superedge_t m_cgraph_edge_to_intraproc_superedge; + /* Data. */ - typedef ordered_hash_map stmt_to_node_t; - stmt_to_node_t m_stmt_to_node_t; + typedef ordered_hash_map bb_to_node_t; + bb_to_node_t m_bb_to_initial_node; + bb_to_node_t m_bb_to_final_node; + + std::map m_node_for_stmt; + std::map<::edge, superedge *> m_edges_for_phis; typedef hash_map 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 m_snode_by_id; }; /* A node within a supergraph. */ @@ -238,10 +223,15 @@ private: class supernode : public dnode { 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; } @@ -250,8 +240,7 @@ class supernode : public dnode { 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); } @@ -259,64 +248,50 @@ class supernode : public dnode void dump_dot (graphviz_out *gv, const dump_args_t &args) const override; void dump_dot_id (pretty_printer *pp) const; - std::unique_ptr 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 to_json () const; - gcall *get_final_call () const - { - gimple *stmt = get_last_stmt (); - if (stmt == nullptr) - return nullptr; - return dyn_cast (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 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 { public: + superedge (supernode *src, supernode *dest, + std::unique_ptr op, + ::edge cfg_edge) + : dedge (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; @@ -324,39 +299,28 @@ class superedge : public dedge 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 op) { m_op = std::move (op); } + + void dump_label_to_pp (pretty_printer *pp, + bool user_facing) const; std::unique_ptr 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 (src, dest), - m_kind (kind) - {} + bool + supports_bulk_merge_p () const; - public: - const enum edge_kind m_kind; +private: + std::unique_ptr m_op; + ::edge m_cfg_edge; }; /* An ID representing an expression at a callsite: @@ -393,399 +357,28 @@ class callsite_expr 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 ::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 ::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 ::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 ::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 (m_src->get_last_stmt ()); - } - - const vec &get_case_labels () const { return m_case_labels; } - - bool implicitly_created_default_p () const; - -private: - auto_vec m_case_labels; -}; - -} // namespace ana - -template <> -template <> -inline bool -is_a_helper ::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 - 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 *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 ::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 *out) - const final override; - -private: - eh_catch m_eh_catch; -}; - -} // namespace ana - -template <> -template <> -inline bool -is_a_helper ::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 *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 ::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 */ diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 44482bfefbe..31f06825944 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -335,14 +335,21 @@ svalue::can_merge_p (const svalue *other, 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) @@ -353,7 +360,7 @@ svalue::can_merge_p (const svalue *other, && 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) @@ -655,9 +662,9 @@ svalue::cmp_ptr (const svalue *sval1, const svalue *sval2) { 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; @@ -1956,7 +1963,7 @@ widening_svalue::dump_to_pp (pretty_printer *pp, bool simple) const { 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, ", "); @@ -1968,7 +1975,7 @@ widening_svalue::dump_to_pp (pretty_printer *pp, bool simple) const 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, ", "); @@ -1984,7 +1991,7 @@ void 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 diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index df236d73606..731c4054d47 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -530,26 +530,30 @@ namespace ana { 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 }; @@ -1272,9 +1276,9 @@ public: /* 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) {} @@ -1289,7 +1293,7 @@ public: 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); } @@ -1300,7 +1304,7 @@ public: bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; - function_point m_point; + const supernode *m_snode; const svalue *m_base_sval; const svalue *m_iter_sval; }; @@ -1312,13 +1316,13 @@ public: 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 ()); @@ -1341,7 +1345,7 @@ public: 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; } @@ -1351,7 +1355,7 @@ public: tree rhs_cst) const; private: - function_point m_point; + const supernode *m_snode; const svalue *m_base_sval; const svalue *m_iter_sval; }; diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc index 1fdfd34cdcd..5d0e7d61585 100644 --- a/gcc/analyzer/varargs.cc +++ b/gcc/analyzer/varargs.cc @@ -198,7 +198,6 @@ public: 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 @@ -218,17 +217,11 @@ public: 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; }; @@ -247,7 +240,6 @@ va_list_state_machine::va_list_state_machine (logger *logger) 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 (stmt)) @@ -257,7 +249,7 @@ va_list_state_machine::on_stmt (sm_context &sm_ctxt, 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; } @@ -270,15 +262,15 @@ va_list_state_machine::on_stmt (sm_context &sm_ctxt, 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; } } @@ -548,15 +540,14 @@ private: 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); } } @@ -564,13 +555,11 @@ va_list_state_machine::on_va_start (sm_context &sm_ctxt, 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 (*this, arg, NULL_TREE, usage_fnname)); } @@ -597,19 +586,18 @@ get_stateful_va_copy_arg (sm_context &sm_ctxt, 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); } } @@ -617,30 +605,28 @@ va_list_state_machine::on_va_copy (sm_context &sm_ctxt, 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"); } } @@ -788,7 +774,8 @@ public: /* 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. */ @@ -822,24 +809,17 @@ public: 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 (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 (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: diff --git a/gcc/digraph.h b/gcc/digraph.h index 6ccd112cee8..6c39199d713 100644 --- a/gcc/digraph.h +++ b/gcc/digraph.h @@ -48,8 +48,36 @@ class dnode 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 m_preds; auto_vec m_succs; + +private: + static unsigned + find_edge_idx (auto_vec &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. */ @@ -59,6 +87,7 @@ class dedge { 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) @@ -68,8 +97,19 @@ class dedge 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 (this)); + m_dest = new_dest; + new_dest->add_in_edge (static_cast (this)); + } + } + + node_t *m_src; + node_t *m_dest; }; /* Abstract base class for a directed graph. @@ -101,6 +141,12 @@ class digraph 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 m_nodes; auto_delete_vec m_edges; }; @@ -182,6 +228,8 @@ digraph::dump_dot_to_pp (pretty_printer *pp, 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, "}"); diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi index 87a2a40215e..05744003d91 100644 --- a/gcc/doc/analyzer.texi +++ b/gcc/doc/analyzer.texi @@ -38,17 +38,75 @@ If the analyzer ICEs before this is written out, one workaround is to use 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 @@ -62,11 +120,11 @@ Nodes in the exploded graph correspond to pairs, as in we're not using the algorithm described in that paper, just the ``exploded graph'' terminology. -We reuse nodes for pairs we've already seen, and avoid +We reuse nodes for 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 nodes gets -larger than a particular multiple of the total number of basic blocks +out if the number of exploded 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. @@ -82,18 +140,8 @@ instance. For example, if we're finding the successors of 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. @@ -113,7 +161,7 @@ are referred to throughout dumps in the form @samp{SN': @var{index}} and 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 @@ -571,7 +619,6 @@ together: OTHER_GCC_ARGS \ -wrapper gdb,--args \ -fdump-analyzer-stderr \ - -fanalyzer-fine-grained \ -fdump-ipa-analyzer=stderr @end smallexample @@ -598,12 +645,6 @@ so that the logging interface is enabled and goes to stderr, which often 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 @@ -612,10 +653,13 @@ 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}. @@ -826,7 +870,7 @@ optimizer touching it). @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 diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index b89fbf8dbbc..deceae341be 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -484,7 +484,6 @@ Objective-C and Objective-C++ Dialects}. -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 @@ -12461,14 +12460,7 @@ debugging issues in this code. @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 @@ -12486,6 +12478,16 @@ events fully located within a system header. 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 @@ -12679,11 +12681,10 @@ The graph is written to @file{@var{file}.state-purge.dot}. @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 diff --git a/gcc/gdbhooks.py b/gcc/gdbhooks.py index a58181002fd..cc17de414f8 100644 --- a/gcc/gdbhooks.py +++ b/gcc/gdbhooks.py @@ -399,7 +399,7 @@ class AnaSupernodePrinter: def to_string (self): result = ' #endif +#ifdef INCLUDE_DEQUE +# include +#endif #ifdef INCLUDE_LIST # include #endif diff --git a/gcc/testsuite/c-c++-common/analyzer/allocation-size-multiline-1.c b/gcc/testsuite/c-c++-common/analyzer/allocation-size-multiline-1.c index bc831947175..99b27d0968f 100644 --- a/gcc/testsuite/c-c++-common/analyzer/allocation-size-multiline-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/allocation-size-multiline-1.c @@ -66,19 +66,21 @@ void test_symbolic (int n) /* { 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++ } } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/bzip2-arg-parse-1.c b/gcc/testsuite/c-c++-common/analyzer/bzip2-arg-parse-1.c index 1f1d8294c33..28bf583b480 100644 --- a/gcc/testsuite/c-c++-common/analyzer/bzip2-arg-parse-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/bzip2-arg-parse-1.c @@ -91,5 +91,8 @@ void test (Cell *argList) /* 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" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/coreutils-cksum-pr108664.c b/gcc/testsuite/c-c++-common/analyzer/coreutils-cksum-pr108664.c index 67dcbce09d0..99851d36eda 100644 --- a/gcc/testsuite/c-c++-common/analyzer/coreutils-cksum-pr108664.c +++ b/gcc/testsuite/c-c++-common/analyzer/coreutils-cksum-pr108664.c @@ -1,6 +1,6 @@ /* { 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; @@ -72,7 +72,7 @@ cksum_slice8(FILE* fp, uint_fast32_t* crc_out, uintmax_t* length_out) 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; } diff --git a/gcc/testsuite/c-c++-common/analyzer/coreutils-group_number.c b/gcc/testsuite/c-c++-common/analyzer/coreutils-group_number.c new file mode 100644 index 00000000000..95fa6a211b0 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/coreutils-group_number.c @@ -0,0 +1,43 @@ +/* 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); + } +} diff --git a/gcc/testsuite/c-c++-common/analyzer/data-model-20.c b/gcc/testsuite/c-c++-common/analyzer/data-model-20.c index dd7996bd160..ccfd14d7475 100644 --- a/gcc/testsuite/c-c++-common/analyzer/data-model-20.c +++ b/gcc/testsuite/c-c++-common/analyzer/data-model-20.c @@ -15,14 +15,14 @@ test (int n) { 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; } } diff --git a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c index 2b3ad8c5fb3..00e1efa0cc1 100644 --- a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c +++ b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c @@ -5,7 +5,7 @@ #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) @@ -60,14 +60,22 @@ redo: 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. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/dot-output.c b/gcc/testsuite/c-c++-common/analyzer/dot-output.c index b1badd7e49d..5e705ca28b0 100644 --- a/gcc/testsuite/c-c++-common/analyzer/dot-output.c +++ b/gcc/testsuite/c-c++-common/analyzer/dot-output.c @@ -42,8 +42,10 @@ int test_2 (void) 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" } } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/fd-symbolic-socket.c b/gcc/testsuite/c-c++-common/analyzer/fd-symbolic-socket.c index c6af0a6487f..af87f458cb9 100644 --- a/gcc/testsuite/c-c++-common/analyzer/fd-symbolic-socket.c +++ b/gcc/testsuite/c-c++-common/analyzer/fd-symbolic-socket.c @@ -39,10 +39,9 @@ void test_close_checked_socket (int type) 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) { @@ -77,8 +76,8 @@ void test_leak_of_bound_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) { diff --git a/gcc/testsuite/c-c++-common/analyzer/fibonacci.c b/gcc/testsuite/c-c++-common/analyzer/fibonacci.c index 5d4a4e02e39..0b24314bc23 100644 --- a/gcc/testsuite/c-c++-common/analyzer/fibonacci.c +++ b/gcc/testsuite/c-c++-common/analyzer/fibonacci.c @@ -6,4 +6,4 @@ int fib (int n) 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\]*" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/flex-with-call-summaries.c b/gcc/testsuite/c-c++-common/analyzer/flex-with-call-summaries.c index b4124c963d1..2c7360fbf3a 100644 --- a/gcc/testsuite/c-c++-common/analyzer/flex-with-call-summaries.c +++ b/gcc/testsuite/c-c++-common/analyzer/flex-with-call-summaries.c @@ -881,7 +881,7 @@ static int yy_get_next_buffer (void) 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) ); diff --git a/gcc/testsuite/c-c++-common/analyzer/flex-without-call-summaries.c b/gcc/testsuite/c-c++-common/analyzer/flex-without-call-summaries.c index e68ac2f3b74..03492078e2f 100644 --- a/gcc/testsuite/c-c++-common/analyzer/flex-without-call-summaries.c +++ b/gcc/testsuite/c-c++-common/analyzer/flex-without-call-summaries.c @@ -3,7 +3,7 @@ /* { 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 } } */ @@ -881,8 +881,8 @@ static int yy_get_next_buffer (void) 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) ); diff --git a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-5.c b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-5.c index bf206394186..eb756a2e365 100644 --- a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-5.c +++ b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-5.c @@ -68,6 +68,8 @@ int nowarn_if_i (int i) 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) @@ -94,6 +96,7 @@ int warn_switch (int i, int a[]) default: return warn_switch (a[1], a + 5); } } +#endif NORETURN void fnoreturn (void); diff --git a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-pr108524-2.c b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-pr108524-2.c index d483d7e643c..8f99c0d2253 100644 --- a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-pr108524-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion-pr108524-2.c @@ -107,7 +107,7 @@ void test_switch_2 (int i) test_switch_2 (1776); break; default: - test_switch_2 (1492); /* { dg-warning "infinite recursion" "" { xfail *-*-* } } */ + test_switch_2 (1492); /* { dg-warning "infinite recursion" } */ break; } } diff --git a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c index 1b67c075eb5..08e52728748 100644 --- a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c @@ -12,10 +12,10 @@ _dl_hwcaps_subdirs_build_bitmask (int subdirs, int active) 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); diff --git a/gcc/testsuite/c-c++-common/analyzer/loop-4.c b/gcc/testsuite/c-c++-common/analyzer/loop-4.c index dd3104eeb2a..76b4cb0e8a6 100644 --- a/gcc/testsuite/c-c++-common/analyzer/loop-4.c +++ b/gcc/testsuite/c-c++-common/analyzer/loop-4.c @@ -11,8 +11,10 @@ void test(void) 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++) { @@ -23,7 +25,7 @@ void test(void) /* { 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++) { @@ -33,7 +35,7 @@ void test(void) __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" } */ } } } diff --git a/gcc/testsuite/c-c++-common/analyzer/loop-n-down-to-1-by-1.c b/gcc/testsuite/c-c++-common/analyzer/loop-n-down-to-1-by-1.c index 1ab8abec893..aef2732e2e7 100644 --- a/gcc/testsuite/c-c++-common/analyzer/loop-n-down-to-1-by-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/loop-n-down-to-1-by-1.c @@ -24,8 +24,8 @@ void test(int n) __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 } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/loop.c b/gcc/testsuite/c-c++-common/analyzer/loop.c index 33ac96c06e2..293f644601a 100644 --- a/gcc/testsuite/c-c++-common/analyzer/loop.c +++ b/gcc/testsuite/c-c++-common/analyzer/loop.c @@ -11,7 +11,7 @@ void test(void) /* (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. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-coreutils.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-coreutils.c index 7af4c3794aa..07166e6dcf9 100644 --- a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-coreutils.c +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-coreutils.c @@ -3,7 +3,7 @@ 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'; } diff --git a/gcc/testsuite/c-c++-common/analyzer/paths-4.c b/gcc/testsuite/c-c++-common/analyzer/paths-4.c index 60b3a0cfd2a..ceba7d7d0fd 100644 --- a/gcc/testsuite/c-c++-common/analyzer/paths-4.c +++ b/gcc/testsuite/c-c++-common/analyzer/paths-4.c @@ -32,22 +32,22 @@ int test_2 (struct state *s) 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; } } diff --git a/gcc/testsuite/c-c++-common/analyzer/pr94362-1.c b/gcc/testsuite/c-c++-common/analyzer/pr94362-1.c index a184636073f..22a23622191 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr94362-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr94362-1.c @@ -1,6 +1,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; diff --git a/gcc/testsuite/c-c++-common/analyzer/pr94851-2.c b/gcc/testsuite/c-c++-common/analyzer/pr94851-2.c index af838de9355..6a3e24afbc8 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr94851-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr94851-2.c @@ -46,7 +46,7 @@ int pamark(void) { 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" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/pr96650-1-notrans.c b/gcc/testsuite/c-c++-common/analyzer/pr96650-1-notrans.c index 94c755540b0..ef8a2ea6854 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr96650-1-notrans.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr96650-1-notrans.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-O2 -fno-analyzer-transitivity" } */ +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ int *wf; diff --git a/gcc/testsuite/c-c++-common/analyzer/pr98628.c b/gcc/testsuite/c-c++-common/analyzer/pr98628.c index f339c495fd4..41c76c74d4c 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr98628.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr98628.c @@ -1,4 +1,4 @@ -/* { dg-additional-options "-O1" } */ +/* { dg-additional-options "-O1 -Wno-analyzer-too-complex" } */ void foo(void *); struct chanset_t { diff --git a/gcc/testsuite/c-c++-common/analyzer/pr99774-1.c b/gcc/testsuite/c-c++-common/analyzer/pr99774-1.c index 184baeef935..f95a37d71d1 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr99774-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr99774-1.c @@ -2,7 +2,7 @@ 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/pragma-2.c b/gcc/testsuite/c-c++-common/analyzer/pragma-2.c index f3098760fb0..6614d2e3075 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pragma-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/pragma-2.c @@ -48,7 +48,7 @@ void test (void) *pp = malloc (16); /* { dg-warning "leak" } */ break; case 1: - free (*pp); + free (*pp); /* { dg-warning "double-free" } */ break; case 2: /* no-op. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/realloc-1.c b/gcc/testsuite/c-c++-common/analyzer/realloc-1.c index 0bb846c6249..f43d3412710 100644 --- a/gcc/testsuite/c-c++-common/analyzer/realloc-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/realloc-1.c @@ -93,4 +93,4 @@ void test_10 (char *s, int n) { __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 } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/sock-1.c b/gcc/testsuite/c-c++-common/analyzer/sock-1.c index 57938c0c0ab..d29d3a4da84 100644 --- a/gcc/testsuite/c-c++-common/analyzer/sock-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/sock-1.c @@ -1,3 +1,4 @@ +/* { dg-additional-options "-fno-exceptions" } */ typedef unsigned int __u32; __extension__ typedef __signed__ long long __s64; __extension__ typedef unsigned long long __u64; diff --git a/gcc/testsuite/c-c++-common/analyzer/sprintf-2.c b/gcc/testsuite/c-c++-common/analyzer/sprintf-2.c index 4e101306ae1..f89bdf8235f 100644 --- a/gcc/testsuite/c-c++-common/analyzer/sprintf-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/sprintf-2.c @@ -3,7 +3,8 @@ extern int sprintf(char* dst, const char* fmt, ...) - __attribute__((__nothrow__)); + __attribute__((__nothrow__)) + __attribute__((nonnull (1, 2))); #include "../../gcc.dg/analyzer/analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/sprintf-concat.c b/gcc/testsuite/c-c++-common/analyzer/sprintf-concat.c index ad744588bc0..decbec29e7d 100644 --- a/gcc/testsuite/c-c++-common/analyzer/sprintf-concat.c +++ b/gcc/testsuite/c-c++-common/analyzer/sprintf-concat.c @@ -31,5 +31,5 @@ test_2 (const char *a, const char *b) 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' " } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/stdarg-sentinel-1.c b/gcc/testsuite/c-c++-common/analyzer/stdarg-sentinel-1.c index f8c1f0eb0f8..be1f39513b5 100644 --- a/gcc/testsuite/c-c++-common/analyzer/stdarg-sentinel-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/stdarg-sentinel-1.c @@ -1,5 +1,3 @@ -/* { dg-additional-options "-Wno-analyzer-too-complex" } */ - #define NULL ((void *)0) void test_sentinel (int arg, ...) diff --git a/gcc/testsuite/c-c++-common/analyzer/strncpy-1.c b/gcc/testsuite/c-c++-common/analyzer/strncpy-1.c index f8b38a58d4b..61f9712a038 100644 --- a/gcc/testsuite/c-c++-common/analyzer/strncpy-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/strncpy-1.c @@ -7,7 +7,9 @@ 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) diff --git a/gcc/testsuite/c-c++-common/analyzer/strstr-1.c b/gcc/testsuite/c-c++-common/analyzer/strstr-1.c index 469e6a817d0..a8edbd87e79 100644 --- a/gcc/testsuite/c-c++-common/analyzer/strstr-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/strstr-1.c @@ -4,7 +4,9 @@ #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) diff --git a/gcc/testsuite/g++.dg/analyzer/analyzer.exp b/gcc/testsuite/g++.dg/analyzer/analyzer.exp index 2e290bfc67f..a6f2f6ad7c9 100644 --- a/gcc/testsuite/g++.dg/analyzer/analyzer.exp +++ b/gcc/testsuite/g++.dg/analyzer/analyzer.exp @@ -29,7 +29,7 @@ if [info exists DEFAULT_CXXFLAGS] then { } # 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 diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C index d2381d0af3d..0a1acfc2fb8 100644 --- a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C +++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C @@ -16,5 +16,3 @@ int main () { /* { 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 } */ diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C index e143645074b..2fa99a37f81 100644 --- a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C +++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C @@ -17,5 +17,3 @@ int main () { /* { 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 } */ diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C index cc177e3d0bb..cb04fa00443 100644 --- a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C +++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C @@ -6,7 +6,7 @@ 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->x = 4; /* { dg-line deref_a } */ /* { dg-warning "dereference of NULL" "" { target *-*-* } deref_a } */ diff --git a/gcc/testsuite/g++.dg/analyzer/pr94028.C b/gcc/testsuite/g++.dg/analyzer/pr94028.C index 53bfa2953ec..432eaec5d9d 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr94028.C +++ b/gcc/testsuite/g++.dg/analyzer/pr94028.C @@ -30,12 +30,12 @@ j * 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" } } diff --git a/gcc/testsuite/g++.dg/analyzer/pr96641.C b/gcc/testsuite/g++.dg/analyzer/pr96641.C index eb11c8584b6..e4d8a8c3c17 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr96641.C +++ b/gcc/testsuite/g++.dg/analyzer/pr96641.C @@ -7,7 +7,7 @@ struct iz : uh { virtual void sx () { - sx (); + sx (); /* { dg-warning "infinite recursion" } */ } }; diff --git a/gcc/testsuite/gcc.dg/analyzer/CWE-131-examples.c b/gcc/testsuite/gcc.dg/analyzer/CWE-131-examples.c index 3bc898cd0cc..24b56ce5cd3 100644 --- a/gcc/testsuite/gcc.dg/analyzer/CWE-131-examples.c +++ b/gcc/testsuite/gcc.dg/analyzer/CWE-131-examples.c @@ -18,7 +18,7 @@ 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 #include #include diff --git a/gcc/testsuite/gcc.dg/analyzer/abs-1.c b/gcc/testsuite/gcc.dg/analyzer/abs-1.c index d6ce8d6afad..d549136c9fc 100644 --- a/gcc/testsuite/gcc.dg/analyzer/abs-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/abs-1.c @@ -15,7 +15,7 @@ hide_long (long x) 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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h index db0140bee81..8d98fef69da 100644 --- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h @@ -17,25 +17,37 @@ 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. @@ -45,36 +57,54 @@ extern void __analyzer_dump_escaped (void); __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. */ diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp index c67dc95ba08..1757cd0f490 100644 --- a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp @@ -30,7 +30,7 @@ if [info exists DEFAULT_CFLAGS] then { } # 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, diff --git a/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c index 949306203c0..393640e30a9 100644 --- a/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c @@ -17,7 +17,7 @@ boxed_malloc (size_t sz) return result; } -boxed_ptr +void boxed_free (boxed_ptr ptr) { free (ptr.value); @@ -287,14 +287,14 @@ void test_22 (void) 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; @@ -304,7 +304,7 @@ int test_25 (void) char global_buffer[100]; /* { dg-message "region created here" } */ -int test_26 (void) +void test_26 (void) { boxed_ptr p; p.value = global_buffer; diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c index 48c4815f527..f309ab683fc 100644 --- a/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c @@ -509,7 +509,7 @@ int consume_two_ints_from_va_list (__builtin_va_list ap) 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; diff --git a/gcc/testsuite/gcc.dg/analyzer/combined-conditionals-1.c b/gcc/testsuite/gcc.dg/analyzer/combined-conditionals-1.c index caac2678bf3..1342e0ae837 100644 --- a/gcc/testsuite/gcc.dg/analyzer/combined-conditionals-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/combined-conditionals-1.c @@ -48,7 +48,7 @@ void test_6 (void) __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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-2.c b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-2.c index ecca9aca83b..4b1f27854cc 100644 --- a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-2.c @@ -22,3 +22,4 @@ test_2 (void) 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 ''" "missing return" { target *-*-* } .-2 } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c index 49259262c4a..86e7c69ba6f 100644 --- a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c @@ -23,3 +23,4 @@ test_2 (void) struct union_wrapper uw2; uw2.u.ptr = malloc (1024); } /* { dg-warning "leak of 'uw2.u.ptr'" } */ +/* { dg-warning "use of uninitialized value ''" "missing return" { target *-*-* } .-1 } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c index f1c6c208405..c63db45531b 100644 --- a/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c @@ -12,7 +12,7 @@ static void __analyzer_only_called_when_flag_b_true (int i) __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; diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c index e7a44d2e353..b7301616edd 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c @@ -178,7 +178,7 @@ struct coord long y; }; -int test_12d (struct coord c) +void test_12d (struct coord c) { struct coord d; d = c; @@ -848,7 +848,7 @@ int test_39 (void) return *ptr; } -int test_40 (int flag) +void test_40 (int flag) { int i; if (flag) diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-15.c b/gcc/testsuite/gcc.dg/analyzer/data-model-15.c index 12e84a12420..82f0f22ae5c 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-15.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-15.c @@ -26,7 +26,7 @@ int test_2 (const struct coord *c1, const struct coord *c2, double r_squared) 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; diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-17.c b/gcc/testsuite/gcc.dg/analyzer/data-model-17.c index d50b84ab43a..556c53ac7ab 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-17.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-17.c @@ -14,7 +14,7 @@ static const config table[2] = { { cb_2 } }; -int deflate (foo_t *s, int which) +void deflate (foo_t *s, int which) { (*(table[which].func))(s); } diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-20a.c b/gcc/testsuite/gcc.dg/analyzer/data-model-20a.c index 767b9912ed9..790485d1790 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-20a.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-20a.c @@ -17,7 +17,7 @@ test (int n) { for (; i >= 0; i--) { free(arr[i]); /* { dg-bogus "double-'free'" } */ } - free(arr); /* { dg-bogus "leak" "" { xfail *-*-* } } */ + free(arr); /* { dg-bogus "leak" } */ return NULL; } } diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-7.c b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c index cb0b33e66c8..f84b79f5fd3 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-7.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c @@ -1,7 +1,7 @@ /* { 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) diff --git a/gcc/testsuite/gcc.dg/analyzer/doom-d_main-IdentifyVersion.c b/gcc/testsuite/gcc.dg/analyzer/doom-d_main-IdentifyVersion.c index 1605047c423..e55c378c7ec 100644 --- a/gcc/testsuite/gcc.dg/analyzer/doom-d_main-IdentifyVersion.c +++ b/gcc/testsuite/gcc.dg/analyzer/doom-d_main-IdentifyVersion.c @@ -147,13 +147,13 @@ IdentifyVersion(void) 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")) { @@ -170,13 +170,13 @@ IdentifyVersion(void) 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")) { @@ -193,13 +193,13 @@ IdentifyVersion(void) 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)) { diff --git a/gcc/testsuite/gcc.dg/analyzer/doom-s_sound-pr108867.c b/gcc/testsuite/gcc.dg/analyzer/doom-s_sound-pr108867.c index ac0a789a7bb..61b8c908299 100644 --- a/gcc/testsuite/gcc.dg/analyzer/doom-s_sound-pr108867.c +++ b/gcc/testsuite/gcc.dg/analyzer/doom-s_sound-pr108867.c @@ -633,7 +633,7 @@ S_getChannel(void* origin, sfxinfo_t* sfxinfo) 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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/edges-1.c b/gcc/testsuite/gcc.dg/analyzer/edges-1.c index 644a2df8daf..4e34612941d 100644 --- a/gcc/testsuite/gcc.dg/analyzer/edges-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/edges-1.c @@ -22,9 +22,8 @@ void test_1 (const char *path, int flag) 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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/error-1.c b/gcc/testsuite/gcc.dg/analyzer/error-1.c index 794a9ae7b42..6a20584ea45 100644 --- a/gcc/testsuite/gcc.dg/analyzer/error-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/error-1.c @@ -65,28 +65,28 @@ void test_6 (int st, const char *str) __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\\\]'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-1.c b/gcc/testsuite/gcc.dg/analyzer/explode-1.c index 7d05f40a544..ba60f27f3bf 100644 --- a/gcc/testsuite/gcc.dg/analyzer/explode-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/explode-1.c @@ -47,11 +47,11 @@ void test (void) { 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. */ diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-2.c b/gcc/testsuite/gcc.dg/analyzer/explode-2.c index c16982f3bc4..3b987e10a4a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/explode-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/explode-2.c @@ -24,7 +24,7 @@ void test (void) 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: diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-3.c b/gcc/testsuite/gcc.dg/analyzer/explode-3.c index 1657836df98..0c7760a098c 100644 --- a/gcc/testsuite/gcc.dg/analyzer/explode-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/explode-3.c @@ -29,11 +29,11 @@ void test (void) { 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. */ diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c index 37c71591246..137e2b71bc7 100644 --- a/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c +++ b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c @@ -37,10 +37,9 @@ void test_close_checked_socket (void) 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) { @@ -76,8 +75,8 @@ void test_leak_of_bound_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_on_datagram_socket_without_bind (void) { diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c index 71dbef6d6e7..4d571dd2240 100644 --- a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c @@ -114,7 +114,7 @@ main (void) new = accept (sock, (struct sockaddr *) &clientname, &size); - if (new < 0) + if (new < 0) /* { dg-bogus "leak" "FIXME" { xfail *-*-* } } */ { perror ("accept"); exit (EXIT_FAILURE); diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c index 1da9634c81d..2ddc7edc26a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c @@ -36,10 +36,9 @@ void test_close_checked_socket (void) 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) { @@ -74,8 +73,8 @@ void test_leak_of_bound_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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c index b0882e0bab0..5afc0d2f536 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c @@ -267,13 +267,13 @@ int *test_23a (int n) 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; @@ -283,7 +283,7 @@ int test_25 (void) 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\\\]" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c index 9f6440b52b2..4158da56039 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c @@ -15,11 +15,12 @@ extern void Py_Dealloc (PyObject *op); 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); diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c index ef88388267b..5ec719656f7 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c @@ -1,7 +1,7 @@ #include #include "analyzer-decls.h" -int test (int flag) +void test (int flag) { int other_flag; if (flag) diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c index 7410a717762..b8cac71e85a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c @@ -22,7 +22,7 @@ void __attribute__((noinline)) callee_2 (int *ptr) *ptr = 42; } -int test_2 (int flag) +void test_2 (int flag) { int i; diff --git a/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c index 418168d413e..aa28a72f53f 100644 --- a/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c @@ -43,7 +43,7 @@ extern void check_uninit (u8 v); /* 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; @@ -74,7 +74,7 @@ static int aac_send_raw_srb(/* [...snip...] */) 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; diff --git a/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-1.c b/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-1.c index 65c7bac1f7e..7a8ad850e49 100644 --- a/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-1.c @@ -1,5 +1,5 @@ /* { 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; }; diff --git a/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-2.c b/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-2.c index 298e4839b98..11f2683a82d 100644 --- a/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/null-deref-pr102671-2.c @@ -36,7 +36,7 @@ PSEUDOVECTOR_TYPEP (union vectorlike_header const *a, int code) { 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))); } diff --git a/gcc/testsuite/gcc.dg/analyzer/pr101143.c b/gcc/testsuite/gcc.dg/analyzer/pr101143.c index bcc0974d4e3..e4db20d8b70 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr101143.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr101143.c @@ -10,7 +10,7 @@ test_malloc (void) return malloc (sizeof (int)); } -void * +void test_alloca (void) { void *p = alloca (sizeof (int)); diff --git a/gcc/testsuite/gcc.dg/analyzer/pr101837.c b/gcc/testsuite/gcc.dg/analyzer/pr101837.c index 1a1f7f63637..a61f1b5ec07 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr101837.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr101837.c @@ -7,4 +7,6 @@ void memcheck(void *ptr) { } 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 ''" "missing return" { target *-*-* } .-1 } */ + +void main() { int max_envvar_len = emalloc(max_envvar_len + 1); } /* { dg-message "use of uninitialized value 'max_envvar_len'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/pr101983-not-main.c b/gcc/testsuite/gcc.dg/analyzer/pr101983-not-main.c index fbf3a393ebb..f152153b024 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr101983-not-main.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr101983-not-main.c @@ -31,7 +31,7 @@ void func(struct list **res) } while (true); } -int not_main() +void not_main() { struct list *res; func(&res); diff --git a/gcc/testsuite/gcc.dg/analyzer/pr103892.c b/gcc/testsuite/gcc.dg/analyzer/pr103892.c index a17c3b7e119..258536a6302 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr103892.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr103892.c @@ -1,4 +1,4 @@ -/* { 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, diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104224.c b/gcc/testsuite/gcc.dg/analyzer/pr104224.c index 1ff5f9eae71..e45950b2ba7 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr104224.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr104224.c @@ -50,8 +50,8 @@ void func5(const int *a, int max) 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; } @@ -66,7 +66,7 @@ void func8(const int *a, int max) { int i; for (i=0; i= 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; @@ -311,7 +311,7 @@ read_alias_file (fname, fname_len) ? 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)) { @@ -328,7 +328,7 @@ read_alias_file (fname, fname_len) 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; diff --git a/gcc/testsuite/gcc.dg/analyzer/pr94579.c b/gcc/testsuite/gcc.dg/analyzer/pr94579.c index 0ab88c2efb5..96d588cb475 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr94579.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr94579.c @@ -2,7 +2,7 @@ struct a *c; struct a { int b; } d() { -} +} /* { dg-warning "use of uninitialized value ''" } */ void e() diff --git a/gcc/testsuite/gcc.dg/analyzer/pr98599-a.c b/gcc/testsuite/gcc.dg/analyzer/pr98599-a.c index 2bbf37b0e6e..0381a69b0eb 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr98599-a.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr98599-a.c @@ -4,5 +4,5 @@ /* { 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(); } diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99771-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99771-1.c index 08449f9249e..c95250cf7ad 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr99771-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr99771-1.c @@ -6,15 +6,15 @@ 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. */ @@ -22,29 +22,29 @@ void test_3 (size_t a, size_t b, size_t c) { *(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\\(\\)'" "warning" } */ /* { dg-message "'malloc\\(\\)' could be NULL" "final event" { target *-*-* } .-1 } */ -} /* { dg-warning "leak of 'malloc\\(\\)'" "warning" } */ - /* { dg-message "'malloc\\(\\)' leaks here" "final event" { target *-*-* } .-1 } */ +} /* { dg-warning "leak of 'malloc\\(\\)'" "warning" { target *-*-* } .-2 } */ + /* { dg-message "'malloc\\(\\)' 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. */ @@ -53,11 +53,11 @@ struct s7 { size_t sz; }; 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 } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c index d9704dee6f2..1e526b947dd 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c @@ -100,8 +100,8 @@ test_10 (void) 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) @@ -111,8 +111,8 @@ 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) diff --git a/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c index ee82bb68243..ae41fee1229 100644 --- a/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c @@ -13,28 +13,28 @@ 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" } */ @@ -46,7 +46,7 @@ static void called_by_test_5 (const char *value) 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'" } */ @@ -57,7 +57,7 @@ static char *called_by_test_6 (void) 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\\\]" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py index d92af8376cc..1282159c265 100644 --- a/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py @@ -62,4 +62,4 @@ def test_state_graph(sarif): 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 diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c index 4d5431b822c..7f0148eb1fb 100644 --- a/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c @@ -113,7 +113,7 @@ __analyzer_test_not_enough_args_2_middle (int placeholder, ...) 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). */ diff --git a/gcc/testsuite/gcc.dg/analyzer/strcmp-1.c b/gcc/testsuite/gcc.dg/analyzer/strcmp-1.c index cc50a6fdba5..6b7d8a2aa46 100644 --- a/gcc/testsuite/gcc.dg/analyzer/strcmp-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/strcmp-1.c @@ -3,28 +3,28 @@ #include #include -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\\)\\.\\.\\." } */ { diff --git a/gcc/testsuite/gcc.dg/analyzer/strcpy-1.c b/gcc/testsuite/gcc.dg/analyzer/strcpy-1.c index 30341061f4c..78075f1da5e 100644 --- a/gcc/testsuite/gcc.dg/analyzer/strcpy-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/strcpy-1.c @@ -33,7 +33,7 @@ char *test_uninitialized (char *dst) extern void external_fn (void *ptr); -char *test_external_fn (void) +void test_external_fn (void) { char src[10]; char dst[10]; diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c b/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c index d20b33e090b..0c3bbf675e4 100644 --- a/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c @@ -26,7 +26,7 @@ test_all_values_covered_implicit_default_1 (enum e x) return 1945; } __analyzer_dump_path (); /* { dg-message "path" } */ -} +} /* { dg-warning "use of uninitialized value ''" } */ int __attribute__((tainted_args)) test_all_values_covered_implicit_default_2 (enum e x) diff --git a/gcc/testsuite/gcc.dg/analyzer/switch.c b/gcc/testsuite/gcc.dg/analyzer/switch.c index 5f42e7f21db..fc63da15834 100644 --- a/gcc/testsuite/gcc.dg/analyzer/switch.c +++ b/gcc/testsuite/gcc.dg/analyzer/switch.c @@ -162,7 +162,7 @@ int test_7 () return 0; } -int test_bitmask_1 (int x) +void test_bitmask_1 (int x) { int flag = 0; if (x & 0x80) @@ -193,7 +193,7 @@ int test_bitmask_1 (int x) } } -int test_bitmask_2 (int x) +void test_bitmask_2 (int x) { int flag = 0; if ((x & 0xf80) == 0x80) diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-assert.c b/gcc/testsuite/gcc.dg/analyzer/taint-assert.c index 855ed5f705f..a858e996a80 100644 --- a/gcc/testsuite/gcc.dg/analyzer/taint-assert.c +++ b/gcc/testsuite/gcc.dg/analyzer/taint-assert.c @@ -336,7 +336,7 @@ struct s 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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c index 21794ce4cf8..06b5f1b4e02 100644 --- a/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c @@ -27,7 +27,7 @@ char test_1(FILE *f) 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; diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/analyzer-torture.exp b/gcc/testsuite/gcc.dg/analyzer/torture/analyzer-torture.exp index b4289bda824..eab32ed36d7 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/analyzer-torture.exp +++ b/gcc/testsuite/gcc.dg/analyzer/torture/analyzer-torture.exp @@ -31,7 +31,7 @@ if [info exists DEFAULT_CFLAGS] then { } # 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 diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c index e25d0c31ea5..387e38931dd 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c @@ -14,7 +14,7 @@ boxed_malloc (size_t sz) return result; } -boxed_ptr __attribute__((noinline)) +void __attribute__((noinline)) boxed_free (boxed_ptr ptr) { free (ptr.value); diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c b/gcc/testsuite/gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c index 5e5a2bf79a5..4fd32b48ee4 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c @@ -1,5 +1,5 @@ /* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */ - +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ #include "../analyzer-decls.h" extern _Bool quit_flag; diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/loop-inc-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/torture/loop-inc-ptr-1.c index afb27185f1b..3276d73cdb1 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/loop-inc-ptr-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/loop-inc-ptr-1.c @@ -1,5 +1,5 @@ /* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */ - +/* { dg-skip-if "" { *-*-* } { "-O3" } { "" } } */ #include "../analyzer-decls.h" void test (int *p) diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/pr102225.c b/gcc/testsuite/gcc.dg/analyzer/torture/pr102225.c index a7c324922fd..33632daf399 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/pr102225.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/pr102225.c @@ -2,5 +2,4 @@ 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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/pr93379.c b/gcc/testsuite/gcc.dg/analyzer/torture/pr93379.c index 6d6ac1433c8..9c6944d7697 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/pr93379.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/pr93379.c @@ -1,3 +1,5 @@ /* { 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 } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/stdarg-4.c b/gcc/testsuite/gcc.dg/analyzer/torture/stdarg-4.c index 8275b0fa9ba..c28beac8059 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/stdarg-4.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/stdarg-4.c @@ -326,4 +326,5 @@ void test_3f_caller (int x, int y, int z) { 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 } */ } diff --git a/gcc/testsuite/gcc.dg/analyzer/untracked-1.c b/gcc/testsuite/gcc.dg/analyzer/untracked-1.c index 9f3a639db5c..ad29c6b06c9 100644 --- a/gcc/testsuite/gcc.dg/analyzer/untracked-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/untracked-1.c @@ -105,7 +105,7 @@ int test_11 (void) 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); diff --git a/gcc/testsuite/gcc.dg/analyzer/use-after-free.c b/gcc/testsuite/gcc.dg/analyzer/use-after-free.c index 76a85f56335..c2731cd5d9e 100644 --- a/gcc/testsuite/gcc.dg/analyzer/use-after-free.c +++ b/gcc/testsuite/gcc.dg/analyzer/use-after-free.c @@ -5,7 +5,7 @@ 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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-3.c b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c index def83006a71..2819aa89ad8 100644 --- a/gcc/testsuite/gcc.dg/analyzer/zlib-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c @@ -46,7 +46,7 @@ static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, 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]; @@ -179,12 +179,12 @@ static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, 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; diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc index c80b2dae66b..c88e35f50f8 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc @@ -188,97 +188,6 @@ public: } }; -/* 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 - clone () const final override - { - return std::make_unique (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 (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 { public: @@ -445,12 +354,13 @@ check_refcnt (const region_model *model, return; const auto &eg = ctxt->get_eg (); - refcnt_stmt_finder finder (*eg, reg_tree); auto pd = std::make_unique (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)); } } diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc index c101c45b541..74e2d73179c 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc @@ -53,19 +53,16 @@ public: 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; @@ -289,14 +286,13 @@ gil_state_machine::gil_state_machine (logger *logger) 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; }; @@ -304,7 +300,7 @@ static bool 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; } @@ -314,7 +310,6 @@ check_for_pyobject (gimple *, tree op, tree, void *data) void gil_state_machine::check_for_pyobject_in_call (sm_context &sm_ctxt, - const supernode *node, const gcall &call, tree callee_fndecl) const { @@ -326,7 +321,7 @@ gil_state_machine::check_for_pyobject_in_call (sm_context &sm_ctxt, 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 (*this, call, callee_fndecl, i)); @@ -339,7 +334,6 @@ gil_state_machine::check_for_pyobject_in_call (sm_context &sm_ctxt, 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 (); @@ -355,7 +349,7 @@ gil_state_machine::on_stmt (sm_context &sm_ctxt, "PyEval_SaveThread"); if (global_state == m_released_gil) { - sm_ctxt.warn (node, stmt, NULL_TREE, + sm_ctxt.warn (NULL_TREE, std::make_unique (*this, call)); sm_ctxt.set_global_state (m_stop); } @@ -377,17 +371,17 @@ gil_state_machine::on_stmt (sm_context &sm_ctxt, { /* 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 (stmt), &d, check_for_pyobject, check_for_pyobject, @@ -404,14 +398,13 @@ gil_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const 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 (*this, op)); sm_ctxt.set_global_state (m_stop); } diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c index 8afce8eefac..ec187860658 100644 --- a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c @@ -107,6 +107,8 @@ static int sco_sock_getsockopt_old_broken(struct socket *sock, int optname, char err = -1; /* [...snip...] */ + + return 0; } static int sco_sock_getsockopt_fixed(struct socket *sock, int optname, char __user *optval, int __user *optlen) @@ -136,4 +138,6 @@ static int sco_sock_getsockopt_fixed(struct socket *sock, int optname, char __us err = -1; /* [...snip...] */ + + return 0; } diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c index 1142cf22655..b14ae8d4992 100644 --- a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c @@ -21,7 +21,7 @@ struct sco_conninfo { /* 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 } */ @@ -34,7 +34,7 @@ int test_1 (char __user *optval, const struct sco_conninfo *in) /* { 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. */ diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c index 239c7d1df5d..8242aed0c9c 100644 --- a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c +++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c @@ -44,8 +44,8 @@ struct aac_srb_reply /* 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; @@ -74,8 +74,8 @@ static int aac_send_raw_srb(/* [...snip...] */ /* [...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; diff --git a/gcc/testsuite/gdc.dg/analyzer/analyzer.exp b/gcc/testsuite/gdc.dg/analyzer/analyzer.exp index 954d9d21b68..2fff2454840 100644 --- a/gcc/testsuite/gdc.dg/analyzer/analyzer.exp +++ b/gcc/testsuite/gdc.dg/analyzer/analyzer.exp @@ -32,7 +32,7 @@ if [info exists DEFAULT_DFLAGS] then { } # 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 diff --git a/gcc/testsuite/gfortran.dg/analyzer/analyzer.exp b/gcc/testsuite/gfortran.dg/analyzer/analyzer.exp index 3e5e59247c3..239b921cfb8 100644 --- a/gcc/testsuite/gfortran.dg/analyzer/analyzer.exp +++ b/gcc/testsuite/gfortran.dg/analyzer/analyzer.exp @@ -33,7 +33,7 @@ if [info exists DEFAULT_FFLAGS] then { } # 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 diff --git a/gcc/testsuite/gfortran.dg/analyzer/uninit-pr63311.f90 b/gcc/testsuite/gfortran.dg/analyzer/uninit-pr63311.f90 index 34cc25da01d..fbf202e0dcc 100644 --- a/gcc/testsuite/gfortran.dg/analyzer/uninit-pr63311.f90 +++ b/gcc/testsuite/gfortran.dg/analyzer/uninit-pr63311.f90 @@ -1,4 +1,5 @@ ! { dg-additional-options "-O0" } +! { dg-additional-options "-Wno-analyzer-too-complex" } MODULE M1 IMPLICIT NONE -- 2.26.3