[gcc r16-6063] analyzer: reimplement supergraph to eliminate function_point and stmt_finder

David Malcolm dmalcolm@gcc.gnu.org
Fri Dec 12 17:28:48 GMT 2025


https://gcc.gnu.org/g:0b786d961d4426db15b3f681f4e7c6fcaeb296c2

commit r16-6063-g0b786d961d4426db15b3f681f4e7c6fcaeb296c2
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Fri Dec 12 12:23:34 2025 -0500

    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 "<return-value>"), which
    found many lurking true +ves in the test suite.  I can fix the wording
    of this case as a follow-up.
    
    Disadvantages:
    
    * The supergraph is much larger than before (one node per gimple stmt,
    rather than per basic block) - but the optimizer that runs after the
    supergraph is built simplifies it somewhat (and I have various ideas for
    future simplifications which I hope will help the analyzer scale).
    
    * all edges in the supergraph are intraprocedural, making "supergraph"
    a misnomer.
    
    Other notes:
    
    * I tried to maintain the behavior of -fanalyzer as closely as possible,
    but there are changes to the testsuite output.  These mostly are places
    where the exploration of the exploded graph leads to nodes not being
    merged as well as the previous implementation on a particular test case,
    leading to the analysis hitting a termination limit and bailing out.
    So I expect the analyzer's behavior to change somewhat.  I had to add
    xfails in various places - but was able to remove xfails in others.
    
    * the testsuite was running with -fanalyzer-call-summaries enabled,
    which is not the default for users.  The new implementation uncovered
    numerous pre-existing bugs in -fanalyzer-call-summaries, so the patch
    disables this within the testsuite, matching the default for users.
    Fixing those bugs can be done separately from the patch.
    
    * the only performance data I have so far is with a debug rather than
    release build.  "wall" time spent in the analyzer shows a slight
    improvement overall, but with one new outlier in the integration
    testsuite that now takes over an hour (specifically,
    qemu-7.2.0/build/target_hexagon_decode.c) but I'd like to go ahead with
    pushing this, and treat that specific slowdown as a bug.
    
    I posted an incomplete version of this before the close of stage 1 here:
      https://gcc.gnu.org/pipermail/gcc-patches/2025-November/700883.html
    
    Although the patch is a very large change to -fanalyzer, the changes are
    confined to that component (apart from a trivial addition of
    INCLUDE_DEQUE/#include <deque> to system.h), so I want to apply this
    patch now in stage 3: it's a big quality-of-life improvement when
    debugging -fanalyzer.
    
    gcc/ChangeLog:
            PR analyzer/122003
            * Makefile.in (ANALYZER_OBJS): Add analyzer/ops.o,
            analyzer/supergraph-fixup-locations.o,
            analyzer/supergraph-simplify.o, and analyzer/supergraph-sorting.o.
            * digraph.h (dnode::add_in_edge): New.
            (dnode::remove_in_edge): New.
            (dnode::add_out_edge): New.
            (dnode::remove_out_edge): New.
            (dnode::m_preds): Make public.
            (dnode::m_succs): Likewise.
            (dnode::find_edge_idx): New.
            (dedge::edge_t): New typedef.
            (dedge::m_src): Make non-const.
            (dedge::m_dest): Likewise.
            (dedge::set_dest): New.
            (digraph::add_any_extra_stmts): New.
            (digraph<GraphTraits>::dump_dot_to_pp): Call it.
            * doc/analyzer.texi: Update for rewrite of supergraph.
            * doc/invoke.texi (fanalyzer-fine-grained): Make this as a no-op
            preserved for backwards compatibility.
            (fanalyzer-simplify-supergraph): Document new option.
            (fdump-analyzer-supergraph): Update for changes to output.
            * gdbhooks.py (AnaSupernodePrinter.to_string): Update for renaming
            of supernode::m_index to supernode::m_id.
            * system.h: Include <deque> if INCLUDE_DEQUE was defined.
    
    gcc/analyzer/ChangeLog:
            PR analyzer/122003
            * analyzer-logging.h (class log_nesting_level): New.
            (log_nesting_level::log_nesting_level): New.
            (log_nesting_level::~log_nesting_level): New.
            * analyzer.cc (is_cxa_end_catch_p): New.
            * analyzer.opt (fdump-analyzer-callgraph): Make this as a no-op
            preserved for backwards compatibility.
            (fanalyzer-fine-grained): Likewise.
            (fanalyzer-simplify-supergraph): New.
            * bounds-checking.cc (strip_types): Update for changes to
            widening_svalue.
            * call-details.h: Include "pending-diagnostic.h".
            * call-info.cc (custom_edge_info::get_dot_attrs): New.
            (call_info::add_events_to_path): Add pending_diagnostic & param.
            Fix indentation.
            * call-info.h (call_info::add_events_to_path): Add
            pending_diagnostic & param.
            * call-string.cc (call_string::element_t::operator==): Reimplement.
            (call_string::element_t::cmp): New.
            (call_string::element_t::get_caller_function): Likewise.
            (call_string::element_t::get_callee_function): Likewise.
            (call_string::element_t::get_call_snode_in_caller): New.
            (call_string::element_t::get_return_snode_in_caller): New.
            (call_string::element_t::get_call_stmt): New.
            (call_string::print): Update for new implementation.
            (call_string::to_json): Likewise.
            (call_string::push_call): Likewise.
            (call_string::count_occurrences_of_function): Likewise.
            (call_string::cmp): Likewise.
            (call_string::get_callee_node): Delete.
            (call_string::get_caller_node): Convert into...
            (call_string::get_return_node_in_caller): ...this new function.
            (call_string::validate): Update for new implementation.
            (call_string::recursive_log): Likewise.
            * call-string.h (class call_superedge): Delete forward decl.
            (class return_superedge): Likewise.
            (class call_and_return_op): New forward decl.
            (struct call_string::element_t): Reimplement using
            call_and_return_op, rather than relying on interprocedural edges
            in the supergraph.
            (class call_string): Likewise.
            * call-summary.cc (call_summary::get_fndecl): Reimplement.
            (call_summary_replay::convert_svalue_from_summary_1): Update for
            changes to widening_svalue.
            * checker-event.cc (event_kind_to_string): Update for renamings
            of event_kind::{call_edge -> call_} and
            event_kind::{return_edge -> return_}.
            (region_creation_event_debug::print_desc): Update for change to
            event_loc_info.
            (state_change_event::state_change_event): Pass in event_loc_info
            rather than stack_depth, and pass it to checker_event ctor.
            (superedge_event::get_callgraph_superedge): Delete.
            (superedge_event::should_filter_p): Reimplement in terms of
            get_any_cfg_edge.
            (superedge_event::get_call_and_return_op): New.
            (superedge_event::superedge_event): Drop m_eedge and
            m_critical_state.  Add assertion that the superedge is non-null.
            (cfg_edge_event::get_cfg_superedge): Delete.
            (cfg_edge_event::cfg_edge_event): Add "op" param, and remove
            assertion refering to kinds of superedge.
            (cfg_edge_event::get_meaning): Reimplement without
            cfg_superedge.
            (cfg_edge_event::get_cfg_edge): New.
            (start_cfg_edge_event::print_desc): Use m_op.  Update for renaming
            of superedge::m_index to superedge::m_id.
            (start_cfg_edge_event::maybe_describe_condition): Reimplement in
            ops.cc as gcond_edge_op::maybe_describe_condition.
            (start_cfg_edge_event::should_print_expr_p): Reimplement in ops.cc
            as gcond_edge_op::should_print_expr_p.
            (call_event::call_event): Update for renaming of event_kind.  Drop
            assertion about superedge kind.
            (call_event::print_desc): Update for consolidation of m_var and
            m_critical_state into a struct.
            (return_event::return_event): Inherit directly from checker_event.
            Drop assertion referring to kinds of superedge.  Initialize m_edge
            and m_call_and_return_op.
            (return_event::print_desc): Update for change to m_critical_state.
            * checker-event.h (enum class event_kind): Rename call_edge to
            call_, and return_edge to return_.
            (state_change_event::state_change_event): Update for changes to
            location-handling in base class ctor.
            (state_change_event::record_critical_state): Drop this, moving
            it to special-cases in the subclasses that need it.
            (state_change_event::get_callgraph_superedge): Delete.
            (superedge_event::get_call_and_return_op): New vfunc decl.
            (superedge_event::m_var, superedge_event::m_critical_state): Drop
            these fields from this class, combining them into a new struct
            and moving the fields to the interprocedural event subclasses
            where they make sense.
            (cfg_edge_event::get_cfg_superedge): Delete.
            (cfg_edge_event::get_cfg_edge): Add.
            (cfg_edge_event::cfg_edge_event): Update for changes to location
            handling in base class ctor.  Add "op".
            (cfg_edge_event::m_op): New field.
            (start_cfg_edge_event::start_cfg_edge_event): Update for changes
            to base class ctor.
            (start_cfg_edge_event::maybe_describe_condition): Drop.
            (end_cfg_edge_event::end_cfg_edge_event): Update for changes to
            base class ctor.
            (catch_cfg_edge_event::catch_cfg_edge_event): Likewise.
            (struct critical_state): New struct.
            (call_event::record_critical_state): New decl.
            (call_event::m_critical_state): New field.
            (class return_event): Inherit from checker_event, rather than
            superedge_event.
            (return_event::get_call_and_return_op): New.
            (return_event::record_critical_state): New.
            (return_event::m_call_and_return_op): New field.
            (return_event::m_critical_state): New field.
            * common.h: Define INCLUDE_SET.
            (class cfg_superedge): Drop forward decl.
            (class switch_cfg_superedge): Likewise.
            (class eh_dispatch_cfg_superedge): Likewise.
            (class eh_dispatch_try_cfg_superedge): Likewise.
            (class eh_dispatch_allowed_cfg_superedge): Likewise.
            (class callgraph_superedge): Likewise.
            (class call_superedge): Likewise.
            (class return_superedge): Likewise.
            (class stmt_finder): Likewise.
            (class function_point): Likewise.
            (class feasibility_state): New forward decl.
            (class uncertainty_t): Likewise.
            (useful_location_p): New.
            (known_function::check_any_preconditions): New.
            (custom_edge_info::get_dot_attrs): New decl.
            (custom_edge_info::add_events_to_path): Add param
            "pending_diagnostic &pd".
            (is_cxa_end_catch_p): New decl.
            * constraint-manager.cc
            (bounded_ranges_manager::get_or_create_ranges_for_switch): Delete.
            (bounded_ranges_manager::create_ranges_for_switch): Delete.
            * constraint-manager.h
            (bounded_ranges_manager::get_or_create_ranges_for_switch): Delete
            decl.
            (bounded_ranges_manager::create_ranges_for_switch): Likewise.
            (bounded_ranges_manager::make_case_label_ranges): Make public for
            use by ops code.
            (bounded_ranges_manager::edge_cache_t): Delete.
            (bounded_ranges_manager::m_edge_cache): Delete.
            * diagnostic-manager.cc (pending_location::pending_location): New
            ctor implementations.
            (pending_location::to_json): New.
            (epath_finder::get_best_epath): Rename param to "target_enode".
            Drop param "target_stmt".  Update for renaming of
            supernode::m_index to m_id.
            (epath_finder::explore_feasible_paths): Drop param "target_stmt".
            (process_worklist_item): Likewise.
            (saved_diagnostic::saved_diagnostic): Pass in param "ploc" by
            rvalue reference and store it in m_ploc.  Drop m_stmt_finder and
            other fields made redundant by m_ploc.
            (saved_diagnostic::get_supernode): New.
            (saved_diagnostic::operator==): Update for changes to
            location-tracking.
            (saved_diagnostic::to_json): Update.
            (saved_diagnostic::dump_as_dot_node): Drop m_stmt.
            (saved_diagnostic::calc_best_epath): Update for change to
            location-tracking.
            (saved_diagnostic::supercedes_p): Likewise.
            (saved_diagnostic::maybe_add_sarif_properties): Likewise.
            (get_emission_location): Delete.
            (diagnostic_manager::add_diagnostic): Pass "ploc" by rvalue
            reference, moving it to the saved_diagnostic.  Update early
            rejection check, and call fixup_location before-hand.
            (class dedupe_key): Drop m_stmt field, and update for changes
            to saved_diagnostic.
            (dedupe_winners::add): Call get_best_epath here, and call
            fixer_for_epath on it.
            (diagnostic_manager::emit_saved_diagnostics): Update for
            changes to saved_diagnostic and supernode.
            (diagnostic_manager::emit_saved_diagnostic): Likewise.
            Use the pending_location from the saved_diagnostic for the
            location of the final event, and for the primary location of the
            diagnostic itself.
            (diagnostic_manager::build_emission_path): Use useful_location_p.
            (state_change_event_creator::on_global_state_change): Update for
            changes to location-tracking.
            (state_change_event_creator::on_state_change): Likewise.
            (struct null_assignment_sm_context): Reimplement within ops.cc.
            (diagnostic_manager::add_events_for_eedge): Reimplement.
            (diagnostic_manager::add_events_for_superedge): Delete in favor of
            control_flow_op::add_any_events_for_eedge.
            (diagnostic_manager::prune_for_sm_diagnostic): Update call/return
            for event_kind renamings, and to use call_and_return_op rathern
            than callgraph_superedge.
            (diagnostic_manager::consolidate_conditions): Port from
            cfg_superedge to get_cfg_edge.
            * diagnostic-manager.h: Include "analyzer/supergraph.h" and
            "analyzer/event-loc-info.h".
            (struct pending_location): Move decl earlier in file.  Replace
            the existing specified ctors with 3 new ones.  Add comments.
            (class pending_location::fixer_for_epath): New.
            (pending_location::get_location): New.
            (pending_location::to_json): New decl.
            (pending_location::m_snode): Drop redundant field.
            (pending_location::m_event_loc_info): New field, replacing m_stmt
            and m_loc.
            (pending_location::m_finder): Replace with...
            (pending_location::m_fixer_for_epath): ...this new field.
            (make_ploc_fixer_for_epath_for_leak_diagnostic): New decl.
            (saved_diagnostic::saved_diagnostic): Pass in param "ploc" by
            rvalue reference and store it in m_ploc.
            (saved_diagnostic::get_supernode): New.
            (saved_diagnostic::m_ploc): New field, replacing m_enode,
            m_snode, m_stmt, m_stmt_finder, and m_loc.
            (diagnostic_manager::add_diagnostic): Pass ploc as rvalue
            reference.
            (diagnostic_manager::add_events_for_superedge): Delete decl.
            * engine.cc: Include "gimple-predict.h" and
            "analyzer/impl-sm-context.h".
            (impl_region_model_context::impl_region_model_context): Drop
            stmt_finder.
            (impl_region_model_context::warn): Convert to...
            (impl_region_model_context::warn_at): ...this.
            (class impl_sm_context): Move to impl-sm-context.h.
            (impl_region_model_context::get_state_map_by_name): Drop
            m_stmt_finder.
            (class leak_stmt_finder): Reimplement as...
            (class leak_ploc_fixer_for_epath): ...this.
            (make_ploc_fixer_for_epath_for_leak_diagnostic): New.
            (returning_from_function_p): Update for supergraph changes.
            (impl_region_model_context::on_state_leak): Port from
            leak_stmt_finder to leak_ploc_fixer_for_epath.
            (impl_region_model_context::on_condition): Update for
            location-handling changes.
            (impl_region_model_context::on_bounded_ranges): Likewise.
            (impl_region_model_context::on_phi): Likewise.
            (impl_region_model_context::get_pending_location_for_diag): New.
            (exploded_node::status_to_str): Add status::special.
            (exploded_node::get_processed_stmt): Delete.
            (exploded_node::dump_dot): Elide state if we have a single
            predecessor  and the state hasn't changed.
            (exploded_node::dump_processed_stmts): Delete.
            (exploded_node::on_stmt): Delete, reimplementing in ops.cc as
            gimple_stmt_op::execute_on_state, call_and_return_op::execute, and
            operation::handle_on_stmt_for_state_machines.
            (exploded_node::on_stmt_pre): Delete, reimplementing in ops.cc as
            call_and_return_op::make.
            (exploded_node::on_stmt_post): Delete.
            (class call_summary_edge_info): Move to ops.cc.
            (exploded_node::replay_call_summaries): Delete.
            (exploded_node::replay_call_summary): Delete.
            (exploded_node::on_edge): Delete.
            (exploded_node::on_longjmp): Eliminate ambiguous "setjmp_point"
            and "next_point" in favor of "point_before_setjmp" and
            "point_after_setjmp".
            (exploded_graph::unwind_from_exception): Update for changes to
            program_point.
            (exploded_node::on_throw): Convert "after_throw_point" to a param.
            (exploded_node::on_resx): Delete.
            (exploded_node::detect_leaks): Update for renaming of
            supernode::return_p to supernode::exit_p, and drop stmt param of
            impl_region_model_context ctor.
            (dynamic_call_info_t::update_model): Delete.
            (dynamic_call_info_t::add_events_to_path): Delete.
            (interprocedural_call::print): New.
            (interprocedural_call::get_dot_attrs): New.
            (interprocedural_call::update_state): New.
            (interprocedural_call::update_model): New.
            (interprocedural_call::add_events_to_path): New.
            (interprocedural_return::print): New.
            (interprocedural_return::get_dot_attrs): New.
            (interprocedural_return::update_state): New.
            (interprocedural_return::update_model): New.
            (interprocedural_return::add_events_to_path): New.
            (rewind_info_t::add_events_to_path): Add pending_diagnostic &
            param.
            (exploded_edge::dump_dot_label): Drop superedge kinds.  Show
            op vs no-op.  Flush before printing any superedge label, and
            escape that label.
            (exploded_edge::maybe_get_stmt): New.
            (exploded_edge::maybe_get_op): New.
            (stats::stats): Update for change to m_num_nodes;
            (stats::log): Likewise.
            (stats::dump): Likewise.
            (stats::get_total_enodes): Likewise.
            (strongly_connected_components::strongly_connected_components):
            Update for changes to supergraph.
            (strongly_connected_components::dump): Show the stack.  Update for
            changes to supernode.
            (strongly_connected_components::to_json): Update for changes to
            supergraph.
            (strongly_connected_components::strong_connect): Rename "index" to
            "id".  Drop superedge kinds.
            (worklist::key_t::cmp): Compare BB indexes before snode ids.
            Drop function_point.
            (exploded_graph::exploded_graph): Update stats initialization.
            (tainted_args_function_info::update_model): Reimplement.
            (tainted_args_function_info::add_events_to_path): Add param.
            (exploded_graph::get_or_create_node): Check for recursion limit
            here, rather than in program_point::on_edge and
            exploded_graph::maybe_create_dynamic_call.  Only merge state
            for points with state_merge_at_p.  Update stats tracking for
            changes to supergraph.  Fix wording of log of state.
            (exploded_graph::get_or_create_per_call_string_data): Update for
            supergraph changes.
            (tainted_args_call_info::update_model): Reimplement.
            (tainted_args_call_info::add_events_to_path): Add param.
            (exploded_graph::process_worklist): Drop assertions that nodes
            have no successors, due to some cases during unwinding exceptions.
            Update call to maybe_process_run_of_before_supernode_enodes to
            call to maybe_process_run_of_enodes, and only at points for
            which state_merge_at_p.  Reimplement "too complex" check.
            (exploded_graph::maybe_process_run_of_before_supernode_enodes):
            Convert to...
            (exploded_graph::maybe_process_run_of_enodes): ...this.  Only
            consider nodes with a single successor in the supergraph and for
            which that superedge supports_bulk_merge_p.  Port state updates to
            using operation::update_state_for_bulk_merger.
            (stmt_requires_new_enode_p): Delete.
            (state_change_requires_new_enode_p): Delete.
            (exploded_graph::maybe_create_dynamic_call): Delete.
            (class impl_path_context): Reimplement in ops.cc.
            (class jump_through_null): Move to region-model.cc.
            (exploded_graph::process_node): Use location_t from supernode,
            rather than trying to have a stmt associated with a supernode.
            Drop switch on program_point kind, instead using the operation, if any,
            from the superedge.
            (exploded_graph::get_or_create_function_stats): Update computation
            of num_supernodes for the function.
            (exploded_graph::print_bar_charts): Update for supergraph changes.
            (exploded_graph::dump_stats): Likewise.
            (exploded_graph::dump_states_for_supernode): Delete.
            (exploded_graph::to_json): Update comment.
            (exploded_path::find_stmt_backwards): Update for supergraph
            reimplementation.
            (exploded_path::feasible_p): Drop "last_stmt".
            (feasibility_state::maybe_update_for_edge): Move most of
            implementation to ops and custom_edge_infos.
            (feasibility_state::update_for_stmt): Delete.
            (supernode_cluster::dump_dot): Update for supernode changes.
            (supernode_cluster::cmp_ptr_ptr): Likewise.
            (exploded_graph::dump_exploded_nodes): Update for
            location-handling changes, and for changes to supergraph
            representation.
            (class viz_callgraph_node): Delete
            (class viz_callgraph_edge): Delete.
            (class viz_callgraph): Delete.
            (class viz_callgraph_cluster): Delete.
            (struct viz_callgraph_traits): Delete.
            (dump_callgraph): Delete.
            (exploded_graph_annotator::exploded_graph_annotator): Update for
            supernode::m_index becoming supernode:m_id.
            (exploded_graph_annotator::add_node_annotations): Reimplement to
            show enodes within the node for the supernode.
            (exploded_graph_annotator::print_enode_port): New.
            (exploded_graph_annotator::print_enode): Add port.
            (exploded_graph_annotator::print_saved_diagnostic): Drop stmt.
            (exploded_graph_annotator::m_enodes_per_snodes): Convert to...
            (exploded_graph_annotator::m_enodes_per_snode_id): ...this, using
            std::vector.
            (maybe_dump_supergraph): New.
            (impl_run_checkers): Create region_model_manager before supergraph
            and pass it to supergraph ctor.  Dump the original form of the
            supergraph, then call fixup_locations, simplify, and sort_nodes on
            the supergraph, dumping it at each stage.  Drop dump_callgraph.
            Replace dump to "NAME.supergraph-eg.dot" with dump to
            "NAME.supergraph.N.eg.dot".
            * event-loc-info.h (event_loc_info::event_loc_info): Add ctors taking
            const exploded_node * and const program_point &.
            * exploded-graph.h: Add include of "analyzer/region-model.h".
            (impl_region_model_context::impl_region_model_context): Add default
            for "stmt" param. Drop "stmt_finder" param.
            (impl_region_model_context::warn): Convert to...
            (impl_region_model_context::warn_at): ...this.
            (impl_region_model_context::get_pending_location_for_diag): New.
            (impl_region_model_context::m_stmt_finder): Drop.
            (struct exploded_node::on_stmt_flags): Drop.
            (exploded_node::on_stmt): Drop.
            (exploded_node::on_stmt_pre): Drop.
            (exploded_node::on_stmt_post): Drop.
            (exploded_node::replay_call_summaries): Drop.
            (exploded_node::replay_call_summary): Drop.
            (exploded_node::on_edge): Drop.
            (exploded_node::on_throw): Add "after_throw_point" param.
            (exploded_node::on_resx): Drop.
            (exploded_node::get_location): New.
            (exploded_node::get_stmt): Drop.
            (exploded_node::get_processed_stmt): Drop.
            (exploded_node::maybe_get_stmt): New decl.
            (exploded_node::maybe_get_op): New decl.
            (class dynamic_call_info_t): Delete.
            (class interprocedural_call): New.
            (class interprocedural_return): New.
            (rewind_info_t::add_events_to_path): Add pending_diagnostic &
            param.
            (rewind_info_t::get_setjmp_point): Replace with...
            (rewind_info_t::get_point_before_setjmp): ...this...
            (rewind_info_t::get_point_after_setjmp): ...and this.
            (stats::m_num_nodes): Convert from an array to a plain int.
            (class strongly_connected_components): Convert from index to id
            throughout.
            (exploded_graph::maybe_process_run_of_before_supernode_enodes):
            Replace with...
            (exploded_graph::maybe_process_run_of_enodes): ...this.
            (exploded_graph::maybe_create_dynamic_call): Delete.
            (exploded_graph::save_diagnostic): Drop stmt_finder param.
            (exploded_graph::dump_states_for_supernode): Drop.
            (exploded_graph::m_PK_AFTER_SUPERNODE_per_snode): Drop.
            (class feasibility_problem): Drop "m_last_stmt".
            (feasibility_state::update_for_stmt): Drop.
            (feasibility_state::get_model): Add non-const accessor.
            (feasibility_state::get_snodes_visited): New accessor.
            (class stmt_finder): Drop.
            * feasible-graph.cc (feasible_node::dump_dot): Drop call to
            dump_processed_stmts.
            (feasible_node::get_state_at_stmt): Drop.
            * impl-sm-context.h: New file, adapted from material in engine.cc.
            * infinite-loop.cc
            (perpetual_start_cfg_edge_event::perpetual_start_cfg_edge_event):
            Add "op" param.
            (perpetual_start_cfg_edge_event::print_desc): Use m_op to describe
            condition.
            (looping_back_event::looping_back_event): Add "op" param.
            (infinite_loop_diagnostic::maybe_add_custom_events_for_superedge):
            Convert to...
            (infinite_loop_diagnostic::maybe_add_custom_events_for_eedge):
            ...this.
            (infinite_loop_diagnostic::add_final_event): Port from
            cfg_superedge to get_any_cfg_edge and operations.  Update for
            location-handling changes.
            (get_in_edge_back_edge): Port from cfg_superedge to
            get_any_cfg_edge.
            (starts_infinite_loop_p): Update for location-handling changes.
            (exploded_graph::detect_infinite_loops): Remove redundant params.
            * infinite-recursion.cc
            (infinite_recursion_diagnostic::add_final_event): Update for
            location-handling changes.
            (infinite_recursion_diagnostic::check_valid_fpath_p): Drop gimple
            param.
            (infinite_recursion_diagnostic::fedge_uses_conjured_svalue_p):
            Port from cfg_superedge to operations.
            (is_entrypoint_p): Update for supergraph changes.
            (exploded_graph::detect_infinite_recursion): Update for
            location-handling changes.
            * kf-lang-cp.cc (kf_operator_new::impl_call_pre): Split out
            code to handle placement-new into...
            (kf_operator_new::check_any_preconditions): ...this...
            (kf_operator_new::get_sized_region_for_placement_new): ...and
            this.
            * ops.cc: New file, albeit with material adatpted from old
            implementation.
            * ops.h: Likewise.
            * pending-diagnostic.cc (pending_diagnostic::add_call_event): Add
            gcall param.  Update for changes to location-handling.
            * pending-diagnostic.h
            (pending_diagnostic::maybe_add_custom_events_for_superedge):
            Convert to...
            (pending_diagnostic::maybe_add_custom_events_for_eedge): ...this.
            (pending_diagnostic::add_call_event): Add "call_stmt" param.
            (pending_diagnostic::check_valid_fpath_p): Drop stmt param.
            * program-point.cc (point_kind_to_string): Delete.
            (function_point::function_point): Delete.
            (function_point::print): Delete.
            (function_point::hash): Delete.
            (function_point::get_function): Delete.
            (function_point::get_stmt): Delete.
            (function_point::get_location): Delete.
            (function_point::final_stmt_p): Delete.
            (function_point::from_function_entry): Delete.
            (function_point::before_supernode): Delete.
            (function_point::print_source_line): Convert to...
            (program_point::print_source_line): ...this.
            (program_point::print): Reimplement.
            (program_point::to_json): Likewise.
            (program_point::push_to_call_stack): Delete.
            (program_point::hash): Reimplement.
            (program_point::get_function_at_depth): Likewise.
            (program_point::on_edge): Delete.
            (function_point::cmp_within_supernode_1): Delete.
            (function_point::cmp_within_supernode): Delete.
            (function_point::cmp): Delete.
            (function_point::cmp_ptr): Delete.
            (function_point::next_stmt): Delete.
            (function_point::get_next): Delete.
            (program_point::origin): Update.
            (program_point::from_function_entry): Update.
            (program_point::get_next): Delete.
            (selftest::test_function_point_equality): Delete.
            (selftest::test_function_point_ordering): Delete.
            (selftest::test_program_point_equality): Update for changes to
            program_point.
            (selftest::analyzer_program_point_cc_tests): Don't call deleted
            function_point tests.
            * program-point.h: Include "analyzer/supergraph.h".
            (class exploded_graph): Drop forward decl.
            (enum point_kind): Drop.
            (point_kind_to_string): Drop decl.
            (class function_point): Delete.
            (program_point::program_point): Take a const supernode *
            rather than a const function_point &.
            (program_point::print_source_line): New decl.
            (program_point::operator==): Update.
            (program_point::get_function_point): Drop.
            (program_point::get_supernode): Reimplement.
            (program_point::get_function): Reimplement.
            (program_point::get_fndecl): Reimplement.
            (program_point::get_stmt): Drop.
            (program_point::get_location): Reimplement.
            (program_point::get_kind): Drop.
            (program_point::get_from_edge): Drop.
            (program_point::get_stmt_idx): Drop.
            (program_point::get_stack_depth): Update.
            (program_point::state_merge_at_p): New.
            (program_point::before_supernode): Drop.
            (program_point::before_stmt): Drop.
            (program_point::after_supernode): Drop.
            (program_point::empty): Drop.
            (program_point::deleted): Drop.
            (program_point::on_edge): Drop.
            (program_point::push_to_call_stack): Drop.
            (program_point::next_stmt): Drop.
            (program_point::get_next): Drop.
            (program_point::m_function_point): Replace with...
            (program_point::m_snode): ...this.
            * program-state.cc (program_state::on_edge): Delete.
            (program_state::push_call): Delete.
            (program_state::returning_call): Delete.
            (program_state::prune_for_point): Port from function_point to
            supernode.  Drop stmt param to impl_region_model_context ctor.
            (selftest::test_sm_state_map): Update for engine borrowing rather
            owning the region_model_manager.
            (selftest::test_program_state_1): Likewise.
            (selftest::test_program_state_2): Likewise.
            (selftest::test_program_state_merging): Likewise.
            (selftest::test_program_state_merging_2): Likewise.
            * program-state.h (program_state::push_call): Delete decl.
            (program_state::returning_call): Delete decl.
            (program_state::on_edge): Delete decl.
            * region-model-manager.cc (region_model_manager::maybe_fold_unaryop):
            Only fold constants if we have a type.
            (region_model_manager::get_or_create_widening_svalue): Port from
            function_point to supernode.
            * region-model-manager.h
            (region_model_manager::get_or_create_widening_svalue): Likewise.
            * region-model.cc
            (poisoned_value_diagnostic::check_valid_fpath_p): Drop code for
            handling function_points within an snode.
            (exception_thrown_from_unrecognized_call::add_events_to_path): Add
            pending_diagnostic param.
            (class jump_through_null): Move here from engine.cc.
            (region_model::on_call_pre): Check for jump through null here,
            rather than in exploded_graph::process_node.
            (region_model::on_setjmp): Add superedge param and pass it to
            setjmp_record ctor.
            (region_model::handle_phi): Delete, in favor of
            phis_for_edge_op::update_state in ops.cc.
            (region_model::update_for_phis): Likewise.
            (region_model::maybe_update_for_edge): Delete.
            (region_model::update_for_call_superedge): Delete.
            (region_model::update_for_return_superedge): Delete.
            (region_model::apply_constraints_for_gcond): Reimplement in ops.cc as
            gcond_edge_op::apply_constraints.
            (has_nondefault_case_for_value_p): Move to ops.cc.
            (has_nondefault_cases_for_all_enum_values_p): Move to ops.cc
            (region_model::apply_constraints_for_gswitch): Reimplement in
            ops.cc as switch_case_op::apply_constraints.
            (class rejected_eh_dispatch): Move to ops.cc.
            (exception_matches_type_p): Move to ops.cc.
            (matches_any_exception_type_p): Move to ops.cc.
            (region_model::apply_constraints_for_eh_dispatch): Reimplement in
            ops.cc as eh_dispatch_edge_op::apply_constraints.
            (region_model::apply_constraints_for_eh_dispatch_try): Reimplement
            in ops.cc as eh_dispatch_try_edge_op::apply_eh_constraints.
            (region_model::apply_constraints_for_eh_dispatch_allowed):
            Reimplement in ops.cc as
            eh_dispatch_allowed_edge_op::apply_eh_constraints.
            (region_model::apply_constraints_for_ggoto): Reimplement in ops.cc
            as ggoto_edge_op::apply_constraints.
            (caller_context::warn): Replace with...
            (caller_context::get_pending_location_for_diag): ...this.
            (region_model::get_or_create_region_for_heap_alloc): Fix
            indentation.
            (region_model_context::warn): New, replacing vfunc with
            shared code that calls get_pending_location_for_diag and warn_at
            vfuncs.
            (engine::engine): Borrow m_mgr rather than own it.
            (seldtest::test_state_merging): Update test for ptrs to different
            base regions becoming unmergeable.
            (selftest::test_widening_constraints): Port from function_point to
            supernode.
            * region-model.h: Include "analyzer/diagnostic-manager.h".
            (region_model::on_setjmp): Add superedge param.
            (region_model::void update_for_phis): Drop decl.
            (region_model::handle_phi): Drop decl.
            (region_model::maybe_update_for_edge): Drop decl.
            (region_model::apply_constraints_for_eh_dispatch_try): Drop decl.
            (region_model::apply_constraints_for_eh_dispatch_allowed): Drop
            decl.
            (region_model::update_for_call_superedge): Drop decl.
            (region_model::update_for_return_superedge): Drop decl.
            (region_model::apply_constraints_for_gcond): Drop decl.
            (region_model::apply_constraints_for_gswitch): Drop decl.
            (region_model::apply_constraints_for_ggoto): Drop decl.
            (region_model::apply_constraints_for_eh_dispatch): Drop decl.
            (region_model_context::warn): Convert from vfunc to func.
            (region_model_context::get_pending_location_for_diag): New vfunc.
            (region_model_context::warn_at): New vfunc.
            (class noop_region_model_context): Update for changes to
            region_model_context.
            (class region_model_context_decorator): Likewise.
            (class annotating_context): Likewise.
            (struct model_merger): Port from function_point to supernode.
            (class engine): Borrow m_mgr rather than own it.
            (class test_region_model_context): Update for changes to
            region_model_context.
            * region.cc (frame_region::get_region_for_local): Update for
            change to supergraph.
            * sm-fd.cc: Drop redundant params throughout.  Pass stmt
            rather than node to the various on_ calls.
            * sm-file.cc: Drop redundant params throughout.
            (register_known_file_functions): Register "*_unlocked" versions of
            functions that I'd missed.
            * sm-malloc.cc: Drop redundant params throughout.
            (deref_before_check::loop_header_p): Reimplement cfg_superedge
            check.
            (malloc_state_machine::on_stmt): Move attribute-handling to...
            (malloc_state_machine::check_call_preconditions): ...this new
            function.
            (maybe_complain_about_deref_before_check): Use
            sm_ctxt.get_emission_location when checking for inlining.
            * sm-pattern-test.cc: Drop redundant params throughout.
            * sm-sensitive.cc: Likewise.
            * sm-signal.cc: Likewise.
            * sm-taint.cc: Likewise.
            * sm.cc: Fix unused param warnings.
            * sm.h: Include "analyzer/analyzer-logging.h".  Drop redundant
            params throughout.
            (state_machine::check_call_preconditions): New vfunc.
            (sm_context::get_state): Drop "stmt" args.
            (sm_context::set_next_state): Likewise.
            (sm_context::on_transition): Drop "stmt" and "node" args.
            (sm_context::warn): Likewise.
            (sm_context::get_emission_location): New vfunc.
            * state-purge.cc: Define INCLUDE_SET.
            (class gimple_op_visitor): Replace function_point and function
            with superedge.
            (state_purge_map::state_purge_map): Iterate through ops on edges,
            rather than on stmts in supernodes.
            (state_purge_map::on_duplicated_node): New.
            (state_purge_map::get_or_create_data_for_decl): Use supernode
            rather than function_point.
            (state_purge_per_ssa_name::state_purge_per_ssa_name): Likewise.
            (state_purge_per_ssa_name::needed_at_point_p): Replace with...
            (state_purge_per_ssa_name::needed_at_supernode_p): ...this.
            (state_purge_per_ssa_name::before_use_stmt): Delete.
            (state_purge_per_ssa_name::add_to_worklist): Use supernode rather
            than function_point.
            (name_used_by_phis_p): Delete.
            (state_purge_per_ssa_name::process_point): Replace with...
            (state_purge_per_ssa_name::process_supernode): ...this.
            (state_purge_per_ssa_name::on_duplicated_node): New.
            (state_purge_per_decl::state_purge_per_decl): Use supernode rather
            than function_point.
            (state_purge_per_decl::add_needed_at): Likewise.
            (state_purge_per_decl::add_pointed_to_at): Likewise.
            (state_purge_per_decl::process_worklists): Likewise.
            (state_purge_per_decl::add_to_worklist): Likewise.
            (state_purge_per_decl::process_point_backwards): Replace with...
            (state_purge_per_decl::process_supernode_backwards): ...this.
            (state_purge_per_decl::process_point_forwards): Replace with...
            (state_purge_per_decl::process_supernode_forwards): ...this.
            (state_purge_per_decl::needed_at_point_p): Replace with...
            (state_purge_per_decl::needed_at_supernode_p): ...this.
            (state_purge_per_decl::on_duplicated_node): New.
            (print_vec_of_names): Drop "within_table" param.
            (state_purge_annotator::add_stmt_annotations): Drop.
            (state_purge_annotator::add_node_annotations): Reimplement.
            * state-purge.h: Convert throughout from function_point to
            supernode.
            (state_purge_map::on_duplicated_node): New decl.
            (state_purge_per_ssa_name::on_duplicated_node): Likewise.
            (state_purge_per_decl::on_duplicated_node): Likewise.
            * store.cc (needs_loop_replay_fixup_p): New.
            (store::loop_replay_fixup): Use it rather than checking for
            SK_WIDENING.
            * supergraph-fixup-locations.cc: New file.
            * supergraph-manipulation.h: New file.
            * supergraph-simplify.cc: New file.
            * supergraph-sorting.cc: New file.
            * supergraph.cc: Define INCLUDE_DEQUE.  Drop include of
            "tree-dfa.h".  Include "diagnostics/file-cache.h"
            and "analyzer/exploded-graph.h".
            (supergraph_call_edge): Delete.
            (control_flow_stmt_p): New.
            (supergraph::supergraph): Add "mgr" param.  Initialize
            m_next_snode_id.  Reimplement.
            (supergraph::populate_for_basic_block): New.
            (supergraph::dump_dot_to_pp): Add auto_cfun sentinel.
            Split up nodes using loop information from the original CFG,
            then by basic block.  Call the node_annotator's
            add_extra_objects vfunc.
            (supergraph::dump_dot_to_gv_for_loop): New.
            (supergraph::dump_dot_to_gv_for_bb): New, based on code in
            dump_dot_to_pp.
            (supergraph::add_node): Drop "returning_call" and "phi_nodes"
            params. Add logger param and logging.  Use m_next_snode_id to
            allow for node deletion.
            (supergraph::add_cfg_edge): Delete.
            (supergraph::add_call_superedge): Delete.
            (supergraph::add_return_superedge): Delete.
            (supergraph::delete_nodes): New.
            (supergraph::add_sedges_for_cfg_edge): New.
            (supernode::dump_dot): Drop output cluster, moving
            add_node_annotations to within the dot node.  Show any SCC id.
            Show m_preserve_p and m_state_merger_node.  Update for renaming of
            supernode::return_p to supernode::exit_p.  Highlight nodes without
            source location information.  Show m_loc and m_stmt_loc.  Show
            source lines, with color for highlight.
            (supernode::dump_dot_id): Update.
            (supernode::to_json): Update.
            (supernode::get_start_location): Delete.
            (supernode::get_end_location): Delete.
            (supernode::get_stmt_index): Delete.
            (supernode::get_label): Delete.
            (edge_kind_to_string): Delete.
            (superedge::dump): Update for supernode::m_index becoming m_id.
            (superedge::dump_dot): Drop ltail/lhead attrs.  Flush after
            dumping the label.
            (superedge::to_json): Reimplement.
            (superedge::get_any_cfg_edge): Delete.
            (superedge::get_any_callgraph_edge): Delete.
            (superedge::preserve_p): New.
            (superedge::supports_bulk_merge_p): New.
            (cfg_superedge::dump_label_to_pp): Delete.
            (superedge::dump_label_to_pp): New.
            (cfg_superedge::get_phi_arg_idx): Delete.
            (cfg_superedge::get_phi_arg): Delete.
            (switch_cfg_superedge::switch_cfg_superedge): Delete.
            (switch_cfg_superedge::dump_label_to_pp): Delete.
            (switch_cfg_superedge::implicitly_created_default_p): Delete.
            (get_catch): Move to ops.cc.
            (eh_dispatch_cfg_superedge::make): Delete in favor of
            eh_dispatch_edge_op::make.
            (eh_dispatch_cfg_superedge::eh_dispatch_cfg_superedge): Delete.
            (eh_dispatch_cfg_superedge::get_eh_status): Delete.
            (eh_dispatch_try_cfg_superedge::dump_label_to_pp): Delete.
            (eh_dispatch_try_cfg_superedge::apply_constraints): Delete.
            (eh_dispatch_allowed_cfg_superedge::eh_dispatch_allowed_cfg_superedge):
            Delete.
            (eh_dispatch_allowed_cfg_superedge::dump_label_to_pp): Delete.
            (eh_dispatch_allowed_cfg_superedge::apply_constraints): Delete.
            (callgraph_superedge::dump_label_to_pp): Delete.
            (callgraph_superedge::get_callee_function): Delete.
            (callgraph_superedge::get_caller_function): Delete
            (callgraph_superedge::get_callee_decl): Delete
            (callgraph_superedge::get_call_stmt): Delete
            (callgraph_superedge::get_caller_decl): Delete
            (callgraph_superedge::get_arg_for_parm): Delete in favor of
            call_and_return_op::get_arg_for_parm in ops.cc.
            (callgraph_superedge::get_parm_for_arg): Delete in favor of
            call_and_return_op::get_parm_for_arg in ops.cc.
            (callgraph_superedge::map_expr_from_caller_to_callee): Delete in
            favor of call_and_return_op::map_expr_from_caller_to_callee in
            ops.cc.
            (callgraph_superedge::map_expr_from_callee_to_caller): Delete in
            favor of call_and_return_op::map_expr_from_callee_to_caller in
            ops.cc.
            * supergraph.h: Include "cfgloop.h" and "analyzer/ops.h".
            (enum edge_kind): Delete.
            (struct supergraph_traits::dump_args_t): Add m_eg.
            (class supergraph): Rewrite leading comment.
            (supergraph::supergraph): Add "mgr" param.
            (supergraph::get_node_for_function_entry): Reimplement.
            (supergraph::get_node_for_function_exit): Reimplement.
            (supergraph::get_node_for_block): Convert to...
            (supergraph::get_initial_node_for_block): ...this.
            (supergraph::get_caller_next_node): Delete.
            (supergraph::get_edge_for_call): Delete.
            (supergraph::get_edge_for_return): Delete.
            (supergraph::get_intraprocedural_edge_for_call): Delete.
            (supergraph::get_edge_for_cfg_edge): Delete.
            (supergraph::get_supernode_for_stmt): Delete.
            (supergraph::get_final_node_for_block): New.
            (supergraph::get_supernode_for_stmt): New.
            (supergraph::get_superedge_for_phis): New.
            (supergraph::get_node_by_index): Delete.
            (supergraph::add_node): Drop "returning_call" and "phi_nodes"
            params.  Add logger param.
            (supergraph::add_cfg_edge): Delete.
            (supergraph::add_call_superedge): Delete.
            (supergraph::add_return_superedge): Delete.
            (supergraph::log_stats): New decl.
            (supergraph::delete_nodes): New decl.
            (supergraph::fixup_locations): New decl.
            (supergraph::simplify): New decl.
            (supergraph::sort_nodes): New decl.
            (supergraph::populate_for_basic_block): New decl.
            (supergraph::add_sedges_for_cfg_edge): New decl.
            (supergraph::dump_dot_to_gv_for_loop): New decl.
            (supergraph::dump_dot_to_gv_for_bb): New decl.
            (supergraph::reorder_nodes_and_ids): New decl.
            (supergraph::bb_to_node_t): Make private.
            (supergraph::m_bb_to_initial_node): Make private.
            (supergraph::m_bb_to_final_node): Make private.
            (supergraph::cgraph_edge_to_node_t): Delete typedef.
            (supergraph::m_cgraph_edge_to_caller_prev_node): Delete.
            (supergraph::m_cgraph_edge_to_caller_next_node): Delete.
            (supergraph::cfg_edge_to_cfg_superedge_t): Delete typedef.
            (supergraph::m_cfg_edge_to_cfg_superedge): Delete.
            (supergraph::cgraph_edge_to_call_superedge_t): Delete typedef.
            (supergraph::m_cgraph_edge_to_call_superedge): Delete
            (supergraph::cgraph_edge_to_return_superedge_t): Delete typedef.
            (supergraph::m_cgraph_edge_to_return_superedge): Delete.
            (supergraph::cgraph_edge_to_intraproc_superedge_t): Delete
            typedef.
            (supergraph::m_cgraph_edge_to_intraproc_superedge): Delete.
            (supergraph::stmt_to_node_t): Delete typedef.
            (supergraph::m_stmt_to_node_t): Replace with...
            (supergraph::m_node_for_stmt): ...this.
            (supergraph::m_edges_for_phis): New field.
            (supergraph::m_next_snode_id): New field.
            (supergraph::m_snode_by_id): New field.
            (supernode::supernode): Drop "returning_call" and "phi_nodes"
            params.  Convert "index" to "id".  Update for changes to fields.
            (supernode::return_p): Rename for clarity to...
            (supernode::exit_p): ...this.
            (supernode::get_start_location): Delete.
            (supernode::get_end_location): Delete.
            (supernode::start_phis): Delete.
            (supernode::get_returning_call): Delete.
            (supernode::print): New.
            (supernode::get_last_stmt): Delete.
            (supernode::get_final_call): Delete.
            (supernode::get_stmt_index): Delete.
            (supernode::get_location): New.
            (supernode::get_label): Convert to trivial accessor.
            (supernode::preserve_p): New.
            (supernode::m_returning_call): Drop field.
            (supernode::m_phi_nodes): Drop field.
            (supernode::m_stmts): Drop field.
            (supernode::m_index): Replace with...
            (supernode::m_id): ...this.
            (supernode::m_loc): New field.
            (supernode::m_stmt_loc): New field.
            (supernode::m_original_id): New field.
            (supernode::m_label): New field.
            (supernode::m_preserve_p): New field.
            (supernode::m_state_merger_node): New field.
            (class superedge): Update leading comment.
            (superedge::superedge): Make public rather than protected.  Drop
            "kind" param.  Add "op" and "cfg_edge" params.  Assert that edge
            is intraprocedural.
            (superedge::m_kind): Drop field.
            (superedge::m_op): New field.
            (superedge::m_cfg_edge): New field.
            (superedge::dump_label_to_pp): Make non-virtual.
            (superedge::get_op): New.
            (superedge::set_op): New.
            (superedge::get_kind): Drop.
            (superedge::get_dest_snode): New accessor.
            (superedge::dyn_cast_cfg_superedge): Delete.
            (superedge::dyn_cast_switch_cfg_superedge): Delete
            (superedge::dyn_cast_eh_dispatch_cfg_superedge): Delete
            (superedge::dyn_cast_eh_dispatch_try_cfg_superedge): Delete
            (superedge::dyn_cast_eh_dispatch_allowed_cfg_superedge): Delete
            (superedge::dyn_cast_callgraph_superedge): Delete
            (superedge::dyn_cast_callgraph_superedge): Delete
            (superedge::dyn_cast_call_superedge): Delete
            (superedge::dyn_cast_call_superedge): Delete
            (superedge::dyn_cast_return_superedge): Delete
            (superedge::dyn_cast_return_superedge): Delete
            (superedge::get_any_cfg_edge): Convert to trivial accessor.
            (superedge::get_any_callgraph_edge): Drop.
            (superedge::preserve_p): New.
            (superedge::supports_bulk_merge_p): New.
            (class callgraph_superedge): Drop.
            (is_a_helper <const callgraph_superedge *>::test): Drop.
            (class call_superedge): Drop.
            (is_a_helper <const call_superedge *>::test): Drop.
            (class return_superedge): Drop.
            (is_a_helper <const return_superedge *>::test): Drop.
            (class cfg_superedge): Drop.
            (class switch_cfg_superedge): Drop.
            (is_a_helper <const switch_cfg_superedge *>::test): Drop.
            (class eh_dispatch_cfg_superedge): Drop.
            (is_a_helper <const eh_dispatch_cfg_superedge *>::test): Drop.
            (class eh_dispatch_try_cfg_superedge): Drop.
            (is_a_helper <const eh_dispatch_try_cfg_superedge *>::test): Drop.
            (class eh_dispatch_allowed_cfg_superedge): Drop.
            (is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::test):
            Drop.
            (dot_annotator::~dot_annotator): Use "= default;".
            (dot_annotator::add_node_annotations): Drop return value and
            "within_table" param.
            (dot_annotator::add_stmt_annotations): Drop.
            (dot_annotator::add_after_node_annotations): Drop.
            (dot_annotator::add_extra_objects): New.
            (supergraph_call_edge): Delete decl.
            (get_ultimate_function_for_cgraph_edge): Delete decl.
            * svalue.cc (svalue::can_merge_p): Reject attempts to merge
            pointers that point to different base regions, except for the case
            where both are string literals.  Update for point change in
            widening_svalue.
            (svalue::cmp_ptr): Update for point change to widening_svalue.
            (widening_svalue::dump_to_pp): Likewise.
            (widening_svalue::print_dump_widget_label): Likewise.
            * svalue.h (struct setjmp_record): Add m_sedge.
            (class widening_svalue): Replace function_point m_point with
            const supernode *m_snode throughout.
            * varargs.cc (va_list_state_machine::on_stmt): Drop redundant
            param.
            (va_list_state_machine::on_va_start): Likewise.  Update for change
            to get_state.
            (va_list_state_machine::check_for_ended_va_list): Likewise.
            (va_list_state_machine::on_va_copy): Likewise.
            (va_list_state_machine::on_va_arg): Likewise.
            (va_list_state_machine::on_va_end): Likewise.
            (va_arg_diagnostic::add_call_event): Update for changes to
            location-tracking.
    
    gcc/testsuite/ChangeLog:
            PR analyzer/122003
            * c-c++-common/analyzer/allocation-size-multiline-1.c: Update for split
            of region creation events.
            * c-c++-common/analyzer/bzip2-arg-parse-1.c: Drop test for enode
            merging.  Add -Wno-analyzer-too-complex.
            * c-c++-common/analyzer/coreutils-cksum-pr108664.c: Add
            -Wno-analyzer-symbol-too-complex.  Add dg-bogus for false +ve seen
            during patch development.
            * c-c++-common/analyzer/coreutils-group_number.c: New test.
            * c-c++-common/analyzer/data-model-20.c: Mark warnings as xfail.
            * c-c++-common/analyzer/deref-before-check-qemu-qtest_rsp_args.c:
            Add xfails.
            * c-c++-common/analyzer/dot-output.c: Update for changes to dumps.
            * c-c++-common/analyzer/fd-symbolic-socket.c: Update for
            improvements to locations of leaks.
            * c-c++-common/analyzer/fibonacci.c: Update regex.
            * c-c++-common/analyzer/flex-with-call-summaries.c: Add xfail.
            * c-c++-common/analyzer/flex-without-call-summaries.c: Add
            -Wno-analyzer-symbol-too-complex.  Add xfail.
            * c-c++-common/analyzer/infinite-recursion-5.c: Disable cases that
            now explode the analysis.
            * c-c++-common/analyzer/infinite-recursion-pr108524-2.c: Remove
            xfail.
            * c-c++-common/analyzer/invalid-shift-1.c: Remove xfails with c++26.
            * c-c++-common/analyzer/ipa-callbacks-1.c: New test.
            * c-c++-common/analyzer/loop-4.c: Expect incorrect UNKNOWN within
            loop.  Update expected number of enodes.
            * c-c++-common/analyzer/loop-n-down-to-1-by-1.c: Expect incorrect
            UNKNOWN within loop.
            * c-c++-common/analyzer/loop.c: Drop xfail.
            * c-c++-common/analyzer/out-of-bounds-coreutils.c: Expect infinite
            loop warning.
            * c-c++-common/analyzer/paths-4.c: Update expected number of
            enodes.
            * c-c++-common/analyzer/pr94362-1.c: Drop -Wno-analyzer-too-complex.
            * c-c++-common/analyzer/pr94851-2.c: Add xfail.
            * c-c++-common/analyzer/pr96650-1-notrans.c: Add
            -Wno-analyzer-too-complex.
            * c-c++-common/analyzer/pr98628.c: Likewise.
            * c-c++-common/analyzer/pr99774-1.c: Likewise.
            * c-c++-common/analyzer/pragma-2.c: Expect double-free warning.
            * c-c++-common/analyzer/realloc-1.c: Move expected location of
            leak from trailing "}" to realloc call.
            * c-c++-common/analyzer/sock-1.c: Add -fno-exceptions.
            * c-c++-common/analyzer/sprintf-2.c: Add __attribute__ nonnull to
            decl.
            * c-c++-common/analyzer/sprintf-concat.c: Move expected location
            of leak of p from sprintf to trailing "}".
            * c-c++-common/analyzer/stdarg-sentinel-1.c: Drop
            -Wno-analyzer-too-complex.
            * c-c++-common/analyzer/strncpy-1.c: Add __attribute__ nonnull to
            decl.
            * c-c++-common/analyzer/strstr-1.c: Likewise.
            * g++.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
            * g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C:
            Update expected messages.
            * g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C:
            Likewise.
            * g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C: Likewise.
            * g++.dg/analyzer/pr94028.C: Move expected location of leak
            warning to where return value of f is discarded within m.
            * g++.dg/analyzer/pr96641.C: Expect infinite recursion warning.
            * gcc.dg/analyzer/CWE-131-examples.c: Add
            -Wno-analyzer-too-complex.
            * gcc.dg/analyzer/abs-1.c (test_2): Fix return type.
            * gcc.dg/analyzer/analyzer-decls.h: Reformat.  Add
            __attribute__ ((nothrow)) to all functions.
            * gcc.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
            * gcc.dg/analyzer/boxed-malloc-1.c: Fix return types.
            * gcc.dg/analyzer/call-summaries-2.c: Likewise.
            * gcc.dg/analyzer/combined-conditionals-1.c: Likewise.
            * gcc.dg/analyzer/compound-assignment-2.c: Expect warning about
            missing return.
            * gcc.dg/analyzer/compound-assignment-3.c: Likewise.
            * gcc.dg/analyzer/conditionals-3.c: Fix return type.
            * gcc.dg/analyzer/data-model-1.c: Likewise.
            * gcc.dg/analyzer/data-model-15.c: Likewise.
            * gcc.dg/analyzer/data-model-17.c: Likewise.
            * gcc.dg/analyzer/data-model-20a.c: Remove xfail from bogus leak.
            * gcc.dg/analyzer/data-model-7.c: Fix return type.
            * gcc.dg/analyzer/doom-d_main-IdentifyVersion.c: Add xfail to some
            of the leak msgs.
            * gcc.dg/analyzer/doom-s_sound-pr108867.c: Add xfail.
            * gcc.dg/analyzer/edges-1.c: Update for improvements to location
            of leak.
            * gcc.dg/analyzer/error-1.c: Fix return type.
            * gcc.dg/analyzer/explode-1.c: Drop xfail.  Expect uninit and
            double-free warnings.
            * gcc.dg/analyzer/explode-2.c: Add xfail.
            * gcc.dg/analyzer/explode-3.c: Drop xfail.  Expect uninit and
            double-free warnings.
            * gcc.dg/analyzer/fd-datagram-socket.c: Move expected location of
            leaks to closing "}"s.
            * gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c: Add
            false +ve leak message, due to not considering that program is
            about to exit.
            * gcc.dg/analyzer/fd-stream-socket.c: Move expected location of
            leaks to closing "}"s.
            * gcc.dg/analyzer/malloc-1.c: Fix return types.
            * gcc.dg/analyzer/malloc-many-paths-2.c: Likewise.
            * gcc.dg/analyzer/malloc-paths-10.c: Likewise.
            * gcc.dg/analyzer/malloc-vs-local-4.c: Likewise.
            * gcc.dg/analyzer/memset-CVE-2017-18549-1.c: Likewise.
            * gcc.dg/analyzer/null-deref-pr102671-1.c: Enable
            -fanalyzer-call-summaries.
            * gcc.dg/analyzer/null-deref-pr102671-2.c: Remove xfail.
            * gcc.dg/analyzer/pr101143.c: Fix return type.
            * gcc.dg/analyzer/pr101837.c: Fix return type.  Add warning about
            missing return.
            * gcc.dg/analyzer/pr101983-not-main.c: Fix return type.
            * gcc.dg/analyzer/pr103892.c: Enable -fanalyzer-call-summaries.
            * gcc.dg/analyzer/pr104224.c: Add xfails.
            * gcc.dg/analyzer/pr104434-nonconst.c: Likewise.
            * gcc.dg/analyzer/pr93032-mztools-signed-char.c: Increase
            exploration limits by a factor of 5.
            * gcc.dg/analyzer/pr93032-mztools-unsigned-char.c: Likewise.
            * gcc.dg/analyzer/pr93355-localealias-feasibility-2.c: Fix return type.
            * gcc.dg/analyzer/pr93355-localealias.c: Add xfail.  Add expected
            leak true +ve and uninit false +ve.
            * gcc.dg/analyzer/pr94579.c: Add warning about missing return.
            * gcc.dg/analyzer/pr98599-a.c: Add missing return stmts.
            * gcc.dg/analyzer/pr99771-1.c: Fix expected locations of leaks.
            * gcc.dg/analyzer/pr99774-2.c: Likewise.
            * gcc.dg/analyzer/sensitive-1.c: Fix return types.
            * gcc.dg/analyzer/state-diagram-1-sarif.py: Update.
            * gcc.dg/analyzer/stdarg-1.c
            (__analyzer_test_not_enough_args_2_middle): Add test coverage for
            wording of call event with variadic args.
            * gcc.dg/analyzer/strcmp-1.c: Fix return types.
            * gcc.dg/analyzer/strcpy-1.c: Likewise.
            * gcc.dg/analyzer/switch-enum-taint-1.c: Add warning about missing
            return.
            * gcc.dg/analyzer/switch.c: Fix return types.
            * gcc.dg/analyzer/taint-assert.c: Likewise.
            * gcc.dg/analyzer/taint-write-offset-1.c: Likewise.
            * gcc.dg/analyzer/torture/analyzer-torture.exp: Drop
            -fanalyzer-call-summaries.
            * gcc.dg/analyzer/torture/boxed-ptr-1.c: Fix return type.
            * gcc.dg/analyzer/torture/fold-ptr-arith-pr105784.c: Add
            -Wno-analyzer-too-complex.
            * gcc.dg/analyzer/torture/loop-inc-ptr-1.c: Skip at -O3 to avoid
            changes to enode count.
            * gcc.dg/analyzer/torture/pr102225.c: Consolidate on one line to
            avoid caring about precise location of leak warning.
            * gcc.dg/analyzer/torture/pr93379.c: Skip on -fno-fat-lto-objects.
            Add warning about uninit.
            * gcc.dg/analyzer/torture/stdarg-4.c: Replace UNKNOWN with
            symbolic sum of params.
            * gcc.dg/analyzer/untracked-1.c: Fix return type.
            * gcc.dg/analyzer/use-after-free.c: Likewise.
            * gcc.dg/analyzer/zlib-3.c: Add xfails.
            * gcc.dg/plugin/analyzer_cpython_plugin.cc
            (class refcnt_stmt_finder): Eliminate.
            (check_refcnt): ...in favor of a call to
            make_ploc_fixer_for_epath_for_leak_diagnostic.
            * gcc.dg/plugin/analyzer_gil_plugin.cc: Update for
            location-handling changes.
            * gcc.dg/plugin/infoleak-CVE-2011-1078-1.c: Add missing
            "return 0;".
            * gcc.dg/plugin/infoleak-CVE-2011-1078-2.c: Fix return types.
            * gcc.dg/plugin/infoleak-CVE-2017-18549-1.c: Likewise.
            * gdc.dg/analyzer/analyzer.exp: Drop -fanalyzer-call-summaries.
            * gfortran.dg/analyzer/analyzer.exp: Likewise.
            * gfortran.dg/analyzer/uninit-pr63311.f90: Add
            -Wno-analyzer-too-complex.
    
    Signed-off-by: David Malcolm <dmalcolm@redhat.com>

Diff:
---
 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                        |  124 +-
 gcc/analyzer/call-string.h                         |   64 +-
 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                      |  627 +---
 gcc/analyzer/program-point.h                       |  221 +-
 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 +-
 .../c-c++-common/analyzer/coreutils-group_number.c |   43 +
 .../c-c++-common/analyzer/data-model-20.c          |    4 +-
 .../deref-before-check-qemu-qtest_rsp_args.c       |   14 +-
 gcc/testsuite/c-c++-common/analyzer/dot-output.c   |    8 +-
 .../c-c++-common/analyzer/fd-symbolic-socket.c     |    9 +-
 gcc/testsuite/c-c++-common/analyzer/fibonacci.c    |    2 +-
 .../analyzer/flex-with-call-summaries.c            |    2 +-
 .../analyzer/flex-without-call-summaries.c         |    6 +-
 .../c-c++-common/analyzer/infinite-recursion-5.c   |    3 +
 .../analyzer/infinite-recursion-pr108524-2.c       |    2 +-
 .../c-c++-common/analyzer/invalid-shift-1.c        |    4 +-
 .../c-c++-common/analyzer/ipa-callbacks-1.c        |   39 +
 gcc/testsuite/c-c++-common/analyzer/loop-4.c       |    6 +-
 .../c-c++-common/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 +-
 gcc/testsuite/c-c++-common/analyzer/pr94362-1.c    |    3 -
 gcc/testsuite/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 +-
 gcc/testsuite/c-c++-common/analyzer/pr99774-1.c    |    2 +-
 gcc/testsuite/c-c++-common/analyzer/pragma-2.c     |    2 +-
 gcc/testsuite/c-c++-common/analyzer/realloc-1.c    |    2 +-
 gcc/testsuite/c-c++-common/analyzer/sock-1.c       |    1 +
 gcc/testsuite/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 -
 gcc/testsuite/c-c++-common/analyzer/strncpy-1.c    |    4 +-
 gcc/testsuite/c-c++-common/analyzer/strstr-1.c     |    4 +-
 gcc/testsuite/g++.dg/analyzer/analyzer.exp         |    2 +-
 ...nalyzer-show-events-in-system-headers-default.C |    2 -
 .../fanalyzer-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/testsuite/gcc.dg/analyzer/CWE-131-examples.c   |    2 +-
 gcc/testsuite/gcc.dg/analyzer/abs-1.c              |    2 +-
 gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h     |   60 +-
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |    2 +-
 gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c     |    8 +-
 gcc/testsuite/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/testsuite/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/testsuite/gcc.dg/analyzer/data-model-20a.c     |    2 +-
 gcc/testsuite/gcc.dg/analyzer/data-model-7.c       |    2 +-
 .../gcc.dg/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/testsuite/gcc.dg/analyzer/fd-datagram-socket.c |    9 +-
 .../fd-glibc-byte-stream-connection-server.c       |    2 +-
 gcc/testsuite/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/testsuite/gcc.dg/analyzer/malloc-paths-10.c    |    2 +-
 gcc/testsuite/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/testsuite/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/testsuite/gcc.dg/analyzer/pr104434-nonconst.c  |    4 +-
 .../gcc.dg/analyzer/pr93032-mztools-signed-char.c  |    2 +-
 .../analyzer/pr93032-mztools-unsigned-char.c       |    2 +-
 .../analyzer/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 +-
 .../gcc.dg/analyzer/torture/analyzer-torture.exp   |    2 +-
 .../gcc.dg/analyzer/torture/boxed-ptr-1.c          |    2 +-
 .../analyzer/torture/fold-ptr-arith-pr105784.c     |    2 +-
 .../gcc.dg/analyzer/torture/loop-inc-ptr-1.c       |    2 +-
 gcc/testsuite/gcc.dg/analyzer/torture/pr102225.c   |    3 +-
 gcc/testsuite/gcc.dg/analyzer/torture/pr93379.c    |    2 +
 gcc/testsuite/gcc.dg/analyzer/torture/stdarg-4.c   |    1 +
 gcc/testsuite/gcc.dg/analyzer/untracked-1.c        |    2 +-
 gcc/testsuite/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/testsuite/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 +-
 gcc/testsuite/gfortran.dg/analyzer/analyzer.exp    |    2 +-
 .../gfortran.dg/analyzer/uninit-pr63311.f90        |    1 +
 177 files changed, 8497 insertions(+), 8652 deletions(-)

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index d62314505c95..f60494ffa441 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 e85d293101d5..cef33a78ae03 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 9f6f7b44224a..1cfd6e4c58bc 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 611b987daecc..4aa3afbea5d8 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 7c51ca27bc5b..04aaee3cdc09 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 c0a9118e658b..272be2399fd1 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 f43114344cf2..1470b95c1bd9 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<call_event> (event_loc_info (get_call_stmt ().location,
-					      caller_fndecl,
-					      stack_depth),
-			      this));
+						   caller_fndecl,
+						   stack_depth),
+				   this));
 }
 
 /* Recreate a call_details instance from this call_info.  */
diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h
index 6548d861bf2f..63fd008cb622 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 0bac8b437346..df156a6fbf82 100644
--- a/gcc/analyzer/call-string.cc
+++ b/gcc/analyzer/call-string.cc
@@ -39,7 +39,8 @@ 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
+	  && m_called_fun == other.m_called_fun);
 }
 
 /* call_string::element_t's inequality operator.  */
@@ -49,16 +50,41 @@ call_string::element_t::operator!= (const call_string::element_t &other) const
   return !(*this == other);
 }
 
+int
+call_string::element_t::cmp (const element_t &a, const element_t &b)
+{
+  if (int src_id_cmp = (a.m_call_sedge->m_src->m_id
+			- b.m_call_sedge->m_src->m_id))
+    return src_id_cmp;
+
+  /* We don't expect multiple call sedges with the same src.  */
+  gcc_assert (a.m_call_sedge->m_dest == b.m_call_sedge->m_dest);
+
+  return a.m_called_fun->funcdef_no - b.m_called_fun->funcdef_no;
+}
+
 function *
 call_string::element_t::get_caller_function () const
 {
-  return m_caller->get_function ();
+  return m_call_sedge->m_src->get_function ();
 }
 
-function *
-call_string::element_t::get_callee_function () const
+const supernode *
+call_string::element_t::get_call_snode_in_caller () const
 {
-  return m_callee->get_function ();
+  return m_call_sedge->m_src;
+}
+
+const supernode *
+call_string::element_t::get_return_snode_in_caller () const
+{
+  return m_call_sedge->m_dest;
+}
+
+const gcall &
+call_string::element_t::get_call_stmt () const
+{
+  return m_call_op->get_gcall ();
 }
 
 /* Print this to PP.  */
@@ -75,17 +101,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<json::value>
@@ -96,36 +123,25 @@ call_string::to_json () const
   for (const call_string::element_t &e : m_elements)
     {
       auto e_obj = std::make_unique<json::object> ();
-      e_obj->set_integer ("src_snode_idx", e.m_callee->m_index);
-      e_obj->set_integer ("dst_snode_idx", e.m_caller->m_index);
-      e_obj->set_string ("funcname", function_name (e.m_caller->m_fun));
+      e_obj->set_integer ("call_sedge_src", e.m_call_sedge->m_src->m_id);
+      e_obj->set_integer ("call_sedge_dest", e.m_call_sedge->m_dest->m_id);
+      e_obj->set_string ("called_fun", function_name (e.m_called_fun));
       arr->append (std::move (e_obj));
     }
 
   return arr;
 }
 
-/* Get or create the call_string resulting from pushing the return
-   superedge for CALL_SEDGE onto the end of this call_string.  */
-
-const call_string *
-call_string::push_call (const supergraph &sg,
-			const call_superedge *call_sedge) const
-{
-  gcc_assert (call_sedge);
-  const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
-  gcc_assert (return_sedge);
-  return push_call (return_sedge->m_dest, return_sedge->m_src);
-}
 
 /* Get or create the call_string resulting from pushing the call
    (caller, callee) onto the end of this call_string.  */
 
 const call_string *
-call_string::push_call (const supernode *caller,
-			const supernode *callee) const
+call_string::push_call (const superedge &call_sedge,
+			const call_and_return_op &call_op,
+			function &called_fun) const
 {
-  call_string::element_t e (caller, callee);
+  call_string::element_t e (&call_sedge, &call_op, &called_fun);
 
   if (const call_string **slot = m_children.get (e))
     return *slot;
@@ -160,10 +176,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;
 }
@@ -196,17 +212,9 @@ call_string::cmp (const call_string &a,
       if (i >= len_b)
 	return -1;
 
-      /* Otherwise, compare the node pairs.  */
-      const call_string::element_t a_node_pair = a[i];
-      const call_string::element_t b_node_pair = b[i];
-      int src_cmp
-	= a_node_pair.m_callee->m_index - b_node_pair.m_callee->m_index;
-      if (src_cmp)
-	return src_cmp;
-      int dest_cmp
-	= a_node_pair.m_caller->m_index - b_node_pair.m_caller->m_index;
-      if (dest_cmp)
-	return dest_cmp;
+      /* Otherwise, compare the elements.  */
+      if (int element_cmp = call_string::element_t::cmp (a[i], b[i]))
+	return element_cmp;
       i++;
       // TODO: test coverage for this
     }
@@ -222,24 +230,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 +250,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 +311,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 f8c6a257cf50..94d968fc29e7 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,25 +42,37 @@ 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)
     {
     }
 
     bool operator== (const element_t &other) const;
     bool operator!= (const element_t &other) const;
 
+    static int cmp (const element_t &a, const element_t &b);
+
     /* Accessors */
     function *get_caller_function () const;
-    function *get_callee_function () const;
-
-    const supernode *m_caller;
-    const supernode *m_callee;
+    function *get_callee_function () const { return m_called_fun; }
+    const supernode *get_call_snode_in_caller () const;
+    const supernode *get_return_snode_in_caller () const;
+    const gcall &get_call_stmt () const;
+
+    const superedge *m_call_sedge;
+    const call_and_return_op *m_call_op;
+    function *m_called_fun;
   };
 
   void print (pretty_printer *pp) const;
@@ -76,11 +81,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 +94,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 +120,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 +129,25 @@ private:
     }
     template <typename T> static inline void remove (T &entry)
     {
-      entry.m_key = element_t (nullptr, nullptr);
+      entry.m_key = element_t (nullptr, nullptr, nullptr);
     }
     static const bool empty_zero_p = true;
     template <typename T> static inline bool is_empty (const T &entry)
     {
-      return entry.m_key.m_caller == nullptr;
+      return entry.m_key.m_call_sedge == nullptr;
     }
     template <typename T> static inline bool is_deleted (const T &entry)
     {
-      return entry.m_key.m_caller == reinterpret_cast<const supernode *> (1);
+      return entry.m_key.m_call_sedge == reinterpret_cast<const superedge *> (1);
     }
     template <typename T> static inline void mark_empty (T &entry)
     {
-      entry.m_key = element_t (nullptr, nullptr);
+      entry.m_key = element_t (nullptr, nullptr, nullptr);
       entry.m_value = nullptr;
     }
     template <typename T> static inline void mark_deleted (T &entry)
     {
-      entry.m_key.m_caller = reinterpret_cast<const supernode *> (1);
+       entry.m_key.m_call_sedge = reinterpret_cast<const superedge *> (1);
     }
   };
 
diff --git a/gcc/analyzer/call-summary.cc b/gcc/analyzer/call-summary.cc
index 14ff560b23a1..7a3d4aac7c68 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 <const widening_svalue *> (summary_sval);
-	const function_point &point = widening_summary_sval->get_point ();
+	const supernode *snode = widening_summary_sval->get_snode ();
 	const svalue *summary_base_sval
 	  = widening_summary_sval->get_base_svalue ();
 	const svalue *caller_base_sval
@@ -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 57e36d470e80..bd62e3887a6e 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 <const gcond *> (last_stmt))
-	{
-	  enum tree_code op = gimple_cond_code (cond_stmt);
-	  tree lhs = gimple_cond_lhs (cond_stmt);
-	  tree rhs = gimple_cond_rhs (cond_stmt);
-	  if (cfg_sedge.false_value_p ())
-	    op = invert_tree_comparison (op, false /* honor_nans */);
-	  return maybe_describe_condition (can_colorize,
-					   lhs, op, rhs);
-	}
-    }
-  return label_text::borrow (nullptr);
-}
-
-/* Subroutine of maybe_describe_condition above.
-
-   Attempt to generate a user-facing description of the condition
-   LHS OP RHS, but only if it is likely to make it easier for the
-   user to understand a condition.  */
-
-label_text
-start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
-						tree lhs,
-						enum tree_code op,
-						tree rhs)
-{
-  /* In theory we could just build a tree via
-       fold_build2 (op, boolean_type_node, lhs, rhs)
-     and print it with %qE on it, but this leads to warts such as
-     parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'.  */
-
-  /* Special-case: describe testing the result of strcmp, as figuring
-     out what the "true" or "false" path is can be confusing to the user.  */
-  if (TREE_CODE (lhs) == SSA_NAME
-      && zerop (rhs))
-    {
-      if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs)))
-	if (is_special_named_call_p (*call, "strcmp", 2))
-	  {
-	    if (op == EQ_EXPR)
-	      return label_text::borrow ("when the strings are equal");
-	    if (op == NE_EXPR)
-	      return label_text::borrow ("when the strings are non-equal");
-	  }
-    }
-
-  /* Only attempt to generate text for sufficiently simple expressions.  */
-  if (!should_print_expr_p (lhs))
-    return label_text::borrow (nullptr);
-  if (!should_print_expr_p (rhs))
-    return label_text::borrow (nullptr);
-
-  /* Special cases for pointer comparisons against NULL.  */
-  if (POINTER_TYPE_P (TREE_TYPE (lhs))
-      && POINTER_TYPE_P (TREE_TYPE (rhs))
-      && zerop (rhs))
-    {
-      if (op == EQ_EXPR)
-	return make_label_text (can_colorize, "when %qE is NULL",
-				lhs);
-      if (op == NE_EXPR)
-	return make_label_text (can_colorize, "when %qE is non-NULL",
-				lhs);
-    }
-
-  return make_label_text (can_colorize, "when %<%E %s %E%>",
-			  lhs, op_symbol_code (op), rhs);
-}
-
-/* Subroutine of maybe_describe_condition.
-
-   Return true if EXPR is we will get suitable user-facing output
-   from %E on it.  */
-
-bool
-start_cfg_edge_event::should_print_expr_p (tree expr)
-{
-  if (TREE_CODE (expr) == SSA_NAME)
-    {
-      if (SSA_NAME_VAR (expr))
-	return should_print_expr_p (SSA_NAME_VAR (expr));
-      else
-	return false;
-    }
-
-  if (DECL_P (expr))
-    return true;
-
-  if (CONSTANT_CLASS_P (expr))
-    return true;
-
-  return false;
-}
-
 /* class catch_cfg_edge_event : public cfg_edge_event.  */
 
 diagnostics::paths::event::meaning
@@ -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 fc51be10c956..f79130ffb382 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 ac3048c796e3..a2d903cbe17c 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 c8cc71593cc0..4b8dae029dde 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 <const bounded_ranges *> case_ranges_vec
-    (gimple_switch_num_labels (switch_stmt));
-
-  for (tree case_label : edge.get_case_labels ())
-    {
-      /* Get the ranges for this case label.  */
-      const bounded_ranges *case_ranges
-	= make_case_label_ranges (switch_stmt, case_label);
-      case_ranges_vec.quick_push (case_ranges);
-    }
-
-  /* Combine all the ranges for each case label into a single collection
-     of ranges.  */
-  const bounded_ranges *all_cases_ranges
-    = get_or_create_union (case_ranges_vec);
-  return all_cases_ranges;
-}
-
 /* Get the bounded_ranges instance for CASE_LABEL within
    SWITCH_STMT.  */
 
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index 38686b7e696b..4e3b3967cbcd 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -172,20 +172,13 @@ template <> struct default_hash_traits<bounded_ranges::key_t>
 
 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<bounded_ranges *>
@@ -241,10 +230,6 @@ private:
   };
   typedef hash_map<bounded_ranges *, bounded_ranges *, traits_t> map_t;
   map_t m_map;
-
-  typedef hash_map<const switch_cfg_superedge *,
-		   const bounded_ranges *> edge_cache_t;
-  edge_cache_t m_edge_cache;
 };
 
 /* An equivalence class within a constraint manager: a set of
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index d3ed085b7129..b7131e2ab097 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<json::object>
+pending_location::to_json () const
+{
+  auto ploc_obj = std::make_unique<json::object> ();
+
+  if (m_enode)
+    ploc_obj->set_integer ("enode", m_enode->m_index);
+  // TODO: also store m_event_loc_info
+  return ploc_obj;
+}
+
 /* State for finding the shortest feasible exploded_path for a
    saved_diagnostic.
    This is shared between all diagnostics, so that we avoid repeating work.  */
@@ -76,7 +109,6 @@ public:
 
   std::unique_ptr<exploded_path>
   get_best_epath (const exploded_node *target_enode,
-		  const gimple *target_stmt,
 		  const pending_diagnostic &pd,
 		  const char *desc, unsigned diag_idx,
 		  std::unique_ptr<feasibility_problem> *out_problem);
@@ -86,7 +118,6 @@ private:
 
   std::unique_ptr<exploded_path>
   explore_feasible_paths (const exploded_node *target_enode,
-			  const gimple *target_stmt,
 			  const pending_diagnostic &pd,
 			  const char *desc, unsigned diag_idx);
   bool
@@ -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<exploded_path> *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<exploded_path>
-epath_finder::get_best_epath (const exploded_node *enode,
-			      const gimple *target_stmt,
+epath_finder::get_best_epath (const exploded_node *target_enode,
 			      const pending_diagnostic &pd,
 			      const char *desc, unsigned diag_idx,
 			      std::unique_ptr<feasibility_problem> *out_problem)
@@ -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<exploded_path> epath
-	    = explore_feasible_paths (enode, target_stmt, pd, desc, diag_idx))
+	    = explore_feasible_paths (target_enode, pd, desc, diag_idx))
 	{
 	  if (logger)
 	    logger->log ("accepting %qs at EN: %i, SN: %i (sd: %i)"
 			 " with feasible path (length: %i)",
-			 desc, enode->m_index, snode_idx, diag_idx,
+			 desc, target_enode->m_index, snode_id, diag_idx,
 			 epath->length ());
 	  return epath;
 	}
@@ -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<exploded_path> epath
-	= std::make_unique<exploded_path> (m_sep->get_shortest_path (enode));
+      auto epath
+	= std::make_unique<exploded_path>
+	    (m_sep->get_shortest_path (target_enode));
       if (epath->feasible_p (logger, out_problem, m_eg.get_engine (), &m_eg))
 	{
 	  if (logger)
 	    logger->log ("accepting %qs at EN: %i, SN: %i (sn: %i)"
 			 " with feasible path (length: %i)",
-			 desc, enode->m_index, snode_idx, diag_idx,
+			 desc, target_enode->m_index, snode_id, diag_idx,
 			 epath->length ());
 	}
       else
@@ -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<exploded_path>
 epath_finder::explore_feasible_paths (const exploded_node *target_enode,
-				      const gimple *target_stmt,
 				      const pending_diagnostic &pd,
 				      const char *desc, unsigned diag_idx)
 {
@@ -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<exploded_path> *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<pending_diagnostic> d,
 				    unsigned idx)
-: m_sm (sm), m_enode (ploc.m_enode), m_snode (ploc.m_snode),
-  m_stmt (ploc.m_stmt),
-  /* stmt_finder could be on-stack; we want our own copy that can
-     outlive that.  */
-  m_stmt_finder (ploc.m_finder ? ploc.m_finder->clone () : nullptr),
-  m_loc (ploc.m_loc),
+: m_sm (sm),
+  m_ploc (std::move (ploc)),
   m_var (var), m_sval (sval), m_state (state),
   m_d (std::move (d)), m_trailing_eedge (nullptr),
   m_idx (idx),
@@ -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<checker_event> 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 <pending_note> 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<pending_diagnostic> d)
 {
   gcc_assert (ploc.m_enode);
-  return add_diagnostic (nullptr, ploc, NULL_TREE, nullptr, 0, std::move (d));
+  return add_diagnostic (nullptr, std::move (ploc),
+			 NULL_TREE, nullptr, 0, std::move (d));
 }
 
 /* Add PN to the most recent saved_diagnostic.  */
@@ -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<state_change_event> (supernode,
+      (std::make_unique<state_change_event> (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<state_change_event> (supernode,
+      (std::make_unique<state_change_event> (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<state_change_event> (supernode,
-					     m_stmt,
-					     stack_depth,
-					     m_sm,
-					     var_new_sval,
-					     from, to,
-					     nullptr,
-					     *m_new_state,
-					     nullptr));
-  }
-
-  void set_next_state (const gimple *stmt,
-		       const svalue *sval,
-		       state_machine::state_t to,
-		       tree origin ATTRIBUTE_UNUSED) final override
-  {
-    state_machine::state_t from = get_state (stmt, sval);
-    if (from != m_sm.get_start_state ())
-      return;
-    if (!is_transition_to_null (to))
-      return;
-
-    const supernode *supernode = m_point->get_supernode ();
-    int stack_depth = m_point->get_stack_depth ();
-
-    m_emission_path->add_event
-      (std::make_unique<state_change_event> (supernode,
-					     m_stmt,
-					     stack_depth,
-					     m_sm,
-					     sval,
-					     from, to,
-					     nullptr,
-					     *m_new_state,
-					     nullptr));
-  }
-
-  void warn (const supernode *, const gimple *,
-	     tree, std::unique_ptr<pending_diagnostic>) final override
-  {
-  }
-  void warn (const supernode *, const gimple *,
-	     const svalue *, std::unique_ptr<pending_diagnostic>) final override
-  {
-  }
-
-  tree get_diagnostic_tree (tree expr) final override
-  {
-    return expr;
-  }
-
-  tree get_diagnostic_tree (const svalue *sval) final override
-  {
-    return m_new_state->m_region_model->get_representative_tree (sval);
-  }
-
-  state_machine::state_t get_global_state () const final override
-  {
-    return 0;
-  }
-
-  void set_global_state (state_machine::state_t) final override
-  {
-    /* No-op.  */
-  }
-
-  void clear_all_per_svalue_state () final override
-  {
-    /* No-op.  */
-  }
-
-  void on_custom_transition (custom_transition *) final override
-  {
-  }
-
-  tree is_zero_assignment (const gimple *stmt) final override
-  {
-    const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
-    if (!assign_stmt)
-     return NULL_TREE;
-    if (const svalue *sval
-	= m_new_state->m_region_model->get_gassign_result (assign_stmt, nullptr))
-      if (tree cst = sval->maybe_get_constant ())
-	if (::zerop(cst))
-	  return gimple_assign_lhs (assign_stmt);
-    return NULL_TREE;
-  }
-
-  const program_state *get_old_program_state () const final override
-  {
-    return m_old_state;
-  }
-  const program_state *get_new_program_state () const final override
-  {
-    return m_new_state;
-  }
-
-  /* We only care about transitions to the "null" state
-     within sm-malloc.  Special-case this.  */
-  static bool is_transition_to_null (state_machine::state_t s)
-  {
-    return !strcmp (s->get_name (), "null");
-  }
-
-  const program_state *m_old_state;
-  const program_state *m_new_state;
-  const gimple *m_stmt;
-  const program_point *m_point;
-  checker_path *m_emission_path;
-  const extrinsic_state &m_ext_state;
-};
-
 /* Subroutine of diagnostic_manager::build_emission_path.
    Add any events for EEDGE to EMISSION_PATH.  */
 
@@ -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 <const gcall *> (stmt);
-	if (call && is_setjmp_call_p (*call))
-	  emission_path->add_event
-	    (std::make_unique<setjmp_event>
-	       (event_loc_info (stmt->location,
-				dst_point.get_fndecl (),
-				dst_stack_depth),
-		dst_node,
-		*call));
-	else
-	  emission_path->add_event
-	    (std::make_unique<statement_event> (stmt,
-						dst_point.get_fndecl (),
-						dst_stack_depth, dst_state));
-
-	/* Create state change events for assignment to NULL.
-	   Iterate through the stmts in dst_enode, adding state change
-	   events for them.  */
-	if (dst_state.m_region_model)
-	  {
-	    log_scope s (get_logger (), "processing run of stmts");
-	    program_state iter_state (dst_state);
-	    program_point iter_point (dst_point);
-	    while (1)
-	      {
-		const gimple *stmt = iter_point.get_stmt ();
-		if (const gassign *assign = dyn_cast<const gassign *> (stmt))
-		  {
-		    const extrinsic_state &ext_state = pb.get_ext_state ();
-		    program_state old_state (iter_state);
-		    iter_state.m_region_model->on_assignment (assign, nullptr);
-		    for (unsigned i = 0; i < ext_state.get_num_checkers (); i++)
+	  unsigned i;
+	  const region *reg;
+	  FOR_EACH_VEC_ELT (interest->m_region_creation, i, reg)
+	    if (const frame_region *frame = reg->maybe_get_frame_region ())
+	      if (frame->get_fndecl () == dst_point.get_fndecl ())
+		{
+		  const region *base_reg = reg->get_base_region ();
+		  if (tree decl = base_reg->maybe_get_decl ())
+		    if (DECL_P (decl)
+			&& useful_location_p (DECL_SOURCE_LOCATION (decl)))
 		      {
-			const state_machine &sm = ext_state.get_sm (i);
-			null_assignment_sm_context sm_ctxt (i, sm,
-							    &old_state,
-							    &iter_state,
-							    stmt,
-							    &iter_point,
-							    emission_path,
-							    pb.get_ext_state ());
-			sm.on_stmt (sm_ctxt, dst_point.get_supernode (), stmt);
-			// TODO: what about phi nodes?
+			emission_path->add_region_creation_events
+			  (pb.get_pending_diagnostic (),
+			   reg, dst_state.m_region_model,
+			   event_loc_info (DECL_SOURCE_LOCATION (decl),
+					   dst_point.get_fndecl (),
+					   dst_stack_depth),
+			   m_verbosity > 3);
 		      }
-		  }
-		iter_point.next_stmt ();
-		if (iter_point.get_kind () == PK_AFTER_SUPERNODE
-		    || (dst_node->m_succs.length () > 1
-			&& (iter_point
-			    == dst_node->m_succs[0]->m_dest->get_point ())))
-		  break;
-	      }
-
-	  }
-      }
-      break;
+		}
+	}
     }
 
   /* Look for changes in dynamic extents, which will identify
@@ -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<catch_cfg_edge_event>
-		   (eedge,
-		    event_loc_info (dst_point.get_supernode ()->get_start_location (),
-				    dst_point.get_fndecl (),
-				    dst_stack_depth),
-		    type));
-		return;
-	      }
-	    else
-	      {
-		/* We have the "uncaught exception" sedge, from eh_dispatch
-		   to a block containing resx.
-		   Don't add any events for this, so that we can consolidate
-		   adjacent stack unwinding events.  */
-		return;
-	      }
-	  }
-
-	emission_path->add_event
-	  (std::make_unique<start_cfg_edge_event>
-	     (eedge,
-	      event_loc_info
-		(last_stmt ? last_stmt->location : UNKNOWN_LOCATION,
-		 src_point.get_fndecl (),
-		 src_stack_depth)));
-	emission_path->add_event
-	  (std::make_unique<end_cfg_edge_event>
-	     (eedge,
-	      event_loc_info (dst_point.get_supernode ()->get_start_location (),
-			      dst_point.get_fndecl (),
-			      dst_stack_depth)));
-      }
-      break;
-
-    case SUPEREDGE_CALL:
-      pd->add_call_event (eedge, emission_path);
-      break;
-
-    case SUPEREDGE_INTRAPROCEDURAL_CALL:
-      {
-	/* TODO: add a subclass for this, or generate events for the
-	   summary.  */
-	emission_path->add_event
-	  (std::make_unique<debug_event>
-	     (event_loc_info (last_stmt
-			      ? last_stmt->location
-			      : UNKNOWN_LOCATION,
-			      src_point.get_fndecl (),
-			      src_stack_depth),
-	      "call summary"));
-      }
-      break;
-
-    case SUPEREDGE_RETURN:
-      {
-	const return_superedge *return_edge
-	  = as_a <const return_superedge *> (eedge.m_sedge);
-
-	const gcall &call_stmt = return_edge->get_call_stmt ();
-	emission_path->add_event
-	  (std::make_unique<return_event>
-	     (eedge,
-	      event_loc_info (call_stmt.location,
-			      dst_point.get_fndecl (),
-			      dst_stack_depth)));
-      }
-      break;
-    }
-}
-
 /* Prune PATH, based on the verbosity level, to the most pertinent
    events for a diagnostic that involves VAR ending in state STATE
    (for state machine SM).
@@ -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 d9cf109080f7..bb32e3852b0b 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<json::object> to_json () const;
+
+  /* The enode with which a diagnostic is to be associated,
+     for tracking feasibility.  */
+  exploded_node *m_enode;
+
+  /* Information to use for the location of the warning,
+     and for the location of the "final warning" in the path.  */
+  event_loc_info m_event_loc_info;
+
+  /* Optional hook for use when eventually emitting the diagnostic
+     for fixing up m_event_loc_info based on the specific epath.  */
+  std::unique_ptr<fixer_for_epath> m_fixer_for_epath;
+};
+
+extern std::unique_ptr<pending_location::fixer_for_epath>
+make_ploc_fixer_for_epath_for_leak_diagnostic (const exploded_graph &eg,
+					       tree var);
+
 /* A to-be-emitted diagnostic stored within diagnostic_manager.  */
 
 class saved_diagnostic
 {
 public:
   saved_diagnostic (const state_machine *sm,
-		    const pending_location &ploc,
+		    pending_location &&ploc,
 		    tree var, const svalue *sval,
 		    state_machine::state_t state,
 		    std::unique_ptr<pending_diagnostic> d,
@@ -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<stmt_finder> m_stmt_finder;
-  location_t m_loc;
+  pending_location m_ploc;
   tree m_var;
   const svalue *m_sval;
   state_machine::state_t m_state;
@@ -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<json::object> to_json () const;
 
   bool add_diagnostic (const state_machine *sm,
-		       const pending_location &ploc,
+		       pending_location &&ploc,
 		       tree var,
 		       const svalue *sval,
 		       state_machine::state_t state,
 		       std::unique_ptr<pending_diagnostic> d);
 
-  bool add_diagnostic (const pending_location &ploc,
+  bool add_diagnostic (pending_location &&ploc,
 		       std::unique_ptr<pending_diagnostic> d);
 
   void add_note (std::unique_ptr<pending_note> pn);
@@ -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 9d22950d79b1..714e6431cd64 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<pending_diagnostic> d,
-				 const stmt_finder *custom_finder)
+impl_region_model_context::warn_at (std::unique_ptr<pending_diagnostic> d,
+				    pending_location &&ploc)
 {
   LOG_FUNC (get_logger ());
-  auto curr_stmt_finder = custom_finder ? custom_finder : m_stmt_finder;
-  if (m_stmt == nullptr && curr_stmt_finder == nullptr)
-    {
-      if (get_logger ())
-	get_logger ()->log ("rejecting diagnostic: no stmt");
-      return false;
-    }
   if (m_eg)
     {
       bool terminate_path = d->terminate_path_p ();
-      pending_location ploc (m_enode_for_diag,
-			     m_enode_for_diag->get_supernode (),
-			     m_stmt,
-			     curr_stmt_finder);
-      if (m_eg->get_diagnostic_manager ().add_diagnostic (ploc,
+      if (m_eg->get_diagnostic_manager ().add_diagnostic (std::move (ploc),
 							  std::move (d)))
 	{
 	  if (m_path_ctxt
@@ -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<pending_diagnostic> d) final override
-  {
-    LOG_FUNC (get_logger ());
-    gcc_assert (d);
-    const svalue *var_old_sval
-      = m_old_state->m_region_model->get_rvalue (var, nullptr);
-    state_machine::state_t current
-      = (var
-	 ? m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ())
-	 : m_old_smap->get_global_state ());
-    bool terminate_path = d->terminate_path_p ();
-    pending_location ploc (m_enode_for_diag, snode, stmt, m_stmt_finder);
-    m_eg.get_diagnostic_manager ().add_diagnostic
-      (&m_sm, ploc,
-       var, var_old_sval, current, std::move (d));
-    if (m_path_ctxt
-	&& terminate_path
-	&& flag_analyzer_suppress_followups)
-      m_path_ctxt->terminate_path ();
-  }
-
-  void warn (const supernode *snode, const gimple *stmt,
-	     const svalue *sval,
-	     std::unique_ptr<pending_diagnostic> d) final override
-  {
-    LOG_FUNC (get_logger ());
-    gcc_assert (d);
-    state_machine::state_t current
-      = (sval
-	 ? m_old_smap->get_state (sval, m_eg.get_ext_state ())
-	 : m_old_smap->get_global_state ());
-    bool terminate_path = d->terminate_path_p ();
-    pending_location ploc (m_enode_for_diag, snode, stmt, m_stmt_finder);
-    m_eg.get_diagnostic_manager ().add_diagnostic
-      (&m_sm, ploc,
-       NULL_TREE, sval, current, std::move (d));
-    if (m_path_ctxt
-	&& terminate_path
-	&& flag_analyzer_suppress_followups)
-      m_path_ctxt->terminate_path ();
-  }
-
-  /* Hook for picking more readable trees for SSA names of temporaries,
-     so that rather than e.g.
-       "double-free of '<unknown>'"
-     we can print:
-       "double-free of 'inbuf.data'".  */
-
-  tree get_diagnostic_tree (tree expr) final override
-  {
-    /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
-       likely to be the least surprising tree to report.  */
-    if (TREE_CODE (expr) != SSA_NAME)
-      return expr;
-    if (SSA_NAME_VAR (expr) != NULL)
-      return expr;
-
-    gcc_assert (m_new_state);
-    const svalue *sval = m_new_state->m_region_model->get_rvalue (expr, nullptr);
-    /* Find trees for all regions storing the value.  */
-    if (tree t = m_new_state->m_region_model->get_representative_tree (sval))
-      return t;
-    else
-      return expr;
-  }
-
-  tree get_diagnostic_tree (const svalue *sval) final override
-  {
-    return m_new_state->m_region_model->get_representative_tree (sval);
-  }
-
-  state_machine::state_t get_global_state () const final override
-  {
-    return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
-  }
-
-  void set_global_state (state_machine::state_t state) final override
-  {
-    m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
-  }
-
-  void clear_all_per_svalue_state () final override
-  {
-    m_new_state->m_checker_states[m_sm_idx]->clear_all_per_svalue_state ();
-  }
-
-  void on_custom_transition (custom_transition *transition) final override
-  {
-    transition->impl_transition (&m_eg,
-				 const_cast<exploded_node *> (m_enode_for_diag),
-				 m_sm_idx);
-  }
-
-  tree is_zero_assignment (const gimple *stmt) final override
-  {
-    const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
-    if (!assign_stmt)
-     return NULL_TREE;
-    impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, m_old_state, m_new_state, nullptr, nullptr, stmt);
-    if (const svalue *sval
-	= m_new_state->m_region_model->get_gassign_result (assign_stmt,
-							    &old_ctxt))
-      if (tree cst = sval->maybe_get_constant ())
-	if (::zerop(cst))
-	  return gimple_assign_lhs (assign_stmt);
-    return NULL_TREE;
-  }
-
-  path_context *get_path_context () const final override
-  {
-    return m_path_ctxt;
-  }
-
-  bool unknown_side_effects_p () const final override
-  {
-    return m_unknown_side_effects;
-  }
-
-  const program_state *get_old_program_state () const final override
-  {
-    return m_old_state;
-  }
-
-  const program_state *get_new_program_state () const final override
-  {
-    return m_new_state;
-  }
-
-  log_user m_logger;
-  exploded_graph &m_eg;
-  exploded_node *m_enode_for_diag;
-  const program_state *m_old_state;
-  program_state *m_new_state;
-  const sm_state_map *m_old_smap;
-  sm_state_map *m_new_smap;
-  path_context *m_path_ctxt;
-  const stmt_finder *m_stmt_finder;
-
-  /* Are we handling an external function with unknown side effects?  */
-  bool m_unknown_side_effects;
-};
-
 bool
 impl_region_model_context::
 get_state_map_by_name (const char *name,
@@ -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<stmt_finder> clone () const final override
-  {
-    return std::make_unique<leak_stmt_finder> (m_eg, m_var);
-  }
+  leak_ploc_fixer_for_epath (const exploded_graph &eg, tree var)
+  : m_eg (eg), m_var (var)
+  {}
 
-  const gimple *find_stmt (const exploded_path &epath)
-    final override
+  void
+  fixup_for_epath (const exploded_path &epath,
+		   pending_location &ploc) const final override
   {
     logger * const logger = m_eg.get_logger ();
     LOG_FUNC (logger);
 
+    /* Handle the interprocedural case where we leak the retval at a return
+       because the caller discards the return value.  */
+    if (m_var
+	&& TREE_CODE (m_var) == RESULT_DECL)
+      {
+	auto &point = ploc.m_enode->get_point ();
+	if (point.get_stack_depth () > 1)
+	  if (point.get_supernode ()->exit_p ())
+	    {
+	      /* Get the program_point for the call within the caller.  */
+	      auto &cs = point.get_call_string ();
+	      auto caller_snode = cs.get_return_node_in_caller ();
+	      gcc_assert (caller_snode);
+	      program_point caller_point (caller_snode, *cs.get_parent ());
+	      ploc.m_event_loc_info = event_loc_info (caller_point);
+	      return;
+	    }
+      }
+
+    /* Handle the case where we have e.g.:
+       |   var = malloc (N);
+       |   var = NULL;
+       which with SSA becomes e.g.:
+       |   var_0 = malloc (N);
+       |   var_1 = nullptr;
+       and thus leads to the leak being found at the enode where "var_0" goes
+       out of scope.
+       Fix up the location of the leak to report it at the write of NULL.  */
     if (m_var && TREE_CODE (m_var) == SSA_NAME)
       {
+	log_scope sentinel (logger, "looking for write to sibling SSA name");
 	/* Locate the final write to this SSA name in the path.  */
 	const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
 
 	int idx_of_def_stmt;
-	bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
-	if (!found)
-	  goto not_found;
+	if (epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt))
+	  {
+	    /* What was the next write to the underlying var
+	       after the SSA name was set? (if any).  */
 
-	/* What was the next write to the underlying var
-	   after the SSA name was set? (if any).  */
+	    for (unsigned idx = idx_of_def_stmt + 1;
+		 idx < epath.m_edges.length ();
+		 ++idx)
+	      {
+		const exploded_edge *eedge = epath.m_edges[idx];
+		if (logger)
+		  logger->log ("eedge[%i]: EN %i -> EN %i",
+			       idx,
+			       eedge->m_src->m_index,
+			       eedge->m_dest->m_index);
+		const gimple *stmt = eedge->maybe_get_stmt ();
+		if (!stmt)
+		  continue;
+		if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+		  {
+		    tree lhs = gimple_assign_lhs (assign);
+		    if (TREE_CODE (lhs) == SSA_NAME
+			&& SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
+		      {
+			if (logger)
+			  logger->log ("using location 0%lx from gassign",
+				       assign->location);
+			ploc.m_event_loc_info.m_loc = assign->location;
+			return;
+		      }
+		  }
+	      }
+	  }
+      }
 
-	for (unsigned idx = idx_of_def_stmt + 1;
-	     idx < epath.m_edges.length ();
-	     ++idx)
+    /* If the epath ends at a function exit node, the location is at
+       the final "}".  Try walking backward along EPATH, looking for a
+       the first suitable stmt with a better location.  */
+    gcc_assert (ploc.m_enode->get_supernode ());
+    const greturn *return_stmt = nullptr;
+    if (ploc.m_enode->get_supernode ()->exit_p ()
+	&& has_return_stmt_p (epath, return_stmt, logger))
+      {
+	/* If we have "return SSA_NAME;" on EPATH, keep track of the
+	   pertinent SSA name as we walk backwards through EPATH.  */
+	tree retval = NULL_TREE;
+	if (return_stmt)
+	  retval = gimple_return_retval (return_stmt);
+
+	log_scope sentinel (logger, "walking backward along epath");
+	int idx;
+	const exploded_edge *eedge;
+	FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, idx, eedge)
 	  {
-	    const exploded_edge *eedge = epath.m_edges[idx];
 	    if (logger)
-	      logger->log ("eedge[%i]: EN %i -> EN %i",
-			   idx,
-			   eedge->m_src->m_index,
-			   eedge->m_dest->m_index);
-	    const exploded_node *dst_node = eedge->m_dest;
-	    const program_point &dst_point = dst_node->get_point ();
-	    const gimple *stmt = dst_point.get_stmt ();
-	    if (!stmt)
-	      continue;
-	    if (const gassign *assign = dyn_cast <const gassign *> (stmt))
 	      {
-		tree lhs = gimple_assign_lhs (assign);
-		if (TREE_CODE (lhs) == SSA_NAME
-		    && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
-		  return assign;
+		logger->log ("eedge[%i]: EN %i -> EN %i",
+			     idx,
+			     eedge->m_src->m_index,
+			     eedge->m_dest->m_index);
+		if (retval)
+		  logger->log ("  retval: %qE", retval);
+	      }
+	    if (auto op = eedge->maybe_get_op ())
+	      {
+		if (retval)
+		  if (auto phis = op->dyn_cast_phis_for_edge_op ())
+		    {
+		      for (auto iter : phis->get_pairs ())
+			if (retval == iter.m_dst)
+			  {
+			    /* We have "PHI(RETVAL = SRC);"
+			       Track SRC instead  */
+			    retval = iter.m_src;
+			    if (logger)
+			      logger->log ("updating retval to %qE", retval);
+			  }
+		    }
+		if (const gimple *stmt = op->maybe_get_stmt ())
+		  if (consider_stmt_location_p (*stmt, retval))
+		    if (useful_location_p (stmt->location))
+		      {
+			if (logger)
+			  logger->log ("using location 0x%lx from stmt",
+				       stmt->location);
+			ploc.m_event_loc_info.m_loc = stmt->location;
+			return;
+		      }
 	      }
 	  }
       }
+  }
 
-  not_found:
+private:
+  static bool
+  has_return_stmt_p (const exploded_path &epath,
+		     const greturn *&out_greturn,
+		     logger *logger)
+  {
+    LOG_SCOPE (logger);
 
-    /* Look backwards for the first statement with a location.  */
-    int i;
+    int idx;
     const exploded_edge *eedge;
-    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, idx, eedge)
       {
-	if (logger)
-	  logger->log ("eedge[%i]: EN %i -> EN %i",
-		       i,
-		       eedge->m_src->m_index,
-		       eedge->m_dest->m_index);
-	const exploded_node *dst_node = eedge->m_dest;
-	const program_point &dst_point = dst_node->get_point ();
-	const gimple *stmt = dst_point.get_stmt ();
-	if (stmt)
-	  if (get_pure_location (stmt->location) != UNKNOWN_LOCATION)
-	    return stmt;
+	if (eedge->m_src->get_stack_depth ()
+	    != eedge->m_dest->get_stack_depth ())
+	  {
+	    /* We have interprocedural activity, and
+	       presumably are no longer in the function where
+	       EPATH terminates.
+	       Give up.  */
+	    return false;
+	  }
+	if (auto op = eedge->maybe_get_op ())
+	  {
+	    switch (op->get_kind ())
+	      {
+	      default:
+		break;
+	      case operation::kind::return_stmt:
+		if (logger)
+		  logger->log ("found return_stmt");
+		out_greturn = &((const greturn_op *)op)->get_greturn ();
+		return true;
+	      case operation::kind::predict_stmt:
+		{
+		  auto &stmt = ((const gimple_stmt_op *)op)->get_stmt ();
+		  switch (gimple_predict_predictor (&stmt))
+		    {
+		    case PRED_TREE_EARLY_RETURN:
+		      /* Assume this is due to a "return;" in the user's
+			 code.  */
+		      if (logger)
+			logger->log ("assuming a return: PRED_TREE_EARLY_RETURN");
+		      return true;
+		    default:
+		      break;
+		    }
+		}
+		break;
+	      }
+	  }
       }
-
-    gcc_unreachable ();
-    return nullptr;
+    return false;
   }
 
-  void update_event_loc_info (event_loc_info &) final override
+  /* When certain statements show up on the epath of a leak
+     at an exit node, if they have locations, these locations
+     tend to be better locations for the leak.
+     Return true for such statements (but without checking their
+     locations).  */
+  static bool
+  consider_stmt_location_p (const gimple &stmt,
+			    tree retval)
   {
-    /* No-op.  */
+    if (retval && TREE_CODE (retval) == SSA_NAME)
+      if (&stmt == SSA_NAME_DEF_STMT (retval))
+	return true;
+
+    switch (stmt.code)
+      {
+      default:
+	break;
+      case GIMPLE_CALL:
+	{
+	  const gcall &call = *as_a <const gcall *> (&stmt);
+	  if (is_cxa_end_catch_p (call))
+	    return true;
+	}
+	break;
+      case GIMPLE_PREDICT:
+      case GIMPLE_RETURN:
+	return true;
+      }
+    return false;
   }
 
-private:
   const exploded_graph &m_eg;
   tree m_var;
 };
 
+std::unique_ptr<pending_location::fixer_for_epath>
+make_ploc_fixer_for_epath_for_leak_diagnostic (const exploded_graph &eg,
+					  tree var)
+{
+  return std::make_unique<leak_ploc_fixer_for_epath> (eg, var);
+}
+
 /* A measurement of how good EXPR is for presenting to the user, so
    that e.g. we can say prefer printing
      "leak of 'tmp.m_ptr'"
@@ -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<leak_ploc_fixer_for_epath> (*m_eg, leaked_tree);
       m_eg->get_diagnostic_manager ().add_diagnostic
-	(&sm, ploc,
+	(&sm, std::move (ploc),
 	 leaked_tree_for_diag, sval, state, std::move (pd));
     }
 }
@@ -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 <const gcall *> (stmt),
-					  state,
-					  path_ctxt,
-					  *called_fn,
-					  *called_fn_data,
-					  &ctxt);
-	  }
-      }
+  if (cs_at_longjmp.length () < cs_at_setjmp.length ())
+    return false;
 
-  bool unknown_side_effects = false;
-  bool terminate_path = false;
+  /* Check that the call strings match, up to the depth of the
+     setjmp point.  */
+  for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
+    if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
+      return false;
 
-  on_stmt_pre (eg, stmt, state, &terminate_path,
-	       &unknown_side_effects, &ctxt);
+  return true;
+}
 
-  if (terminate_path)
-    return on_stmt_flags::terminate_path ();
+/* A pending_diagnostic subclass for complaining about bad longjmps,
+   where the enclosing function of the "setjmp" has returned (and thus
+   the stack frame no longer exists).  */
 
-  int sm_idx;
-  sm_state_map *smap;
-  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
-    {
-      const state_machine &sm = eg.get_ext_state ().get_sm (sm_idx);
-      const sm_state_map *old_smap
-	= old_state.m_checker_states[sm_idx];
-      sm_state_map *new_smap = state->m_checker_states[sm_idx];
-      impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
-			       old_smap, new_smap, path_ctxt, nullptr,
-			       unknown_side_effects);
-
-      /* Allow the state_machine to handle the stmt.  */
-      if (sm.on_stmt (sm_ctxt, snode, stmt))
-	unknown_side_effects = false;
-    }
+class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
+{
+public:
+  stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
+		 const program_point &setjmp_point)
+  : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
+    m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
+  {}
 
-  if (path_ctxt->terminate_path_p ())
-    return on_stmt_flags::terminate_path ();
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_stale_setjmp_buffer;
+  }
 
-  on_stmt_post (stmt, state, unknown_side_effects, &ctxt);
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    return ctxt.warn ("%qs called after enclosing function of %qs has returned",
+		      get_user_facing_name (m_longjmp_call),
+		      get_user_facing_name (m_setjmp_call));
+  }
 
-  return on_stmt_flags ();
-}
+  const char *get_kind () const final override
+  { return "stale_jmp_buf"; }
 
-/* Handle the pre-sm-state part of STMT, modifying STATE in-place.
-   Write true to *OUT_TERMINATE_PATH if the path should be terminated.
-   Write true to *OUT_UNKNOWN_SIDE_EFFECTS if the stmt has unknown
-   side effects.  */
-
-void
-exploded_node::on_stmt_pre (exploded_graph &eg,
-			    const gimple *stmt,
-			    program_state *state,
-			    bool *out_terminate_path,
-			    bool *out_unknown_side_effects,
-			    region_model_context *ctxt)
-{
-  /* Handle special-case calls that require the full program_state.  */
-  if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
-    {
-      const gcall &call = *call_stmt;
-      if (is_special_named_call_p (call, "__analyzer_dump", 0))
-	{
-	  /* Handle the builtin "__analyzer_dump" by dumping state
-	     to stderr.  */
-	  state->dump (eg.get_ext_state (), true);
-	  return;
-	}
-      else if (is_special_named_call_p (call, "__analyzer_dump_sarif", 0))
-	{
-	  state->dump_sarif (eg.get_ext_state ());
-	  return;
-	}
-      else if (is_special_named_call_p (call, "__analyzer_dump_dot", 0))
-	{
-	  state->dump_dot (eg.get_ext_state ());
-	  return;
-	}
-      else if (is_special_named_call_p (call, "__analyzer_dump_state", 2))
-	{
-	  state->impl_call_analyzer_dump_state (call, eg.get_ext_state (),
-						ctxt);
-	  return;
-	}
-      else if (is_setjmp_call_p (call))
-	{
-	  state->m_region_model->on_setjmp (call, this, ctxt);
-	  if (ctxt)
-	    ctxt->maybe_did_work ();
-	  return;
-	}
-      else if (is_longjmp_call_p (call))
-	{
-	  on_longjmp (eg, call, state, ctxt);
-	  *out_terminate_path = true;
-	  if (ctxt)
-	    ctxt->maybe_did_work ();
-	  return;
-	}
-      else if (is_cxa_throw_p (call))
-	{
-	  on_throw (eg, call, state, false, ctxt);
-	  *out_terminate_path = true;
-	  return;
-	}
-      else if (is_cxa_rethrow_p (call))
-	{
-	  on_throw (eg, call, state, true, ctxt);
-	  *out_terminate_path = true;
-	  return;
-	}
-    }
-  else if (const gresx *resx = dyn_cast <const gresx *> (stmt))
-    {
-      on_resx (eg, *resx, state, ctxt);
-      *out_terminate_path = true;
-      return;
-    }
-
-  /* Otherwise, defer to m_region_model.  */
-  state->m_region_model->on_stmt_pre (stmt,
-				      out_unknown_side_effects,
-				      ctxt);
-}
-
-/* Handle the post-sm-state part of STMT, modifying STATE in-place.  */
-
-void
-exploded_node::on_stmt_post (const gimple *stmt,
-			     program_state *state,
-			     bool unknown_side_effects,
-			     region_model_context *ctxt)
-{
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    state->m_region_model->on_call_post (*call, unknown_side_effects, ctxt);
-}
-
-/* A concrete call_info subclass representing a replay of a call summary.  */
-
-class call_summary_edge_info : public call_info
-{
-public:
-  call_summary_edge_info (const call_details &cd,
-			  const function &called_fn,
-			  call_summary &summary,
-			  const extrinsic_state &ext_state)
-  : call_info (cd, called_fn),
-    m_called_fn (called_fn),
-    m_summary (summary),
-    m_ext_state (ext_state)
-  {}
-
-  bool update_state (program_state *state,
-		     const exploded_edge *,
-		     region_model_context *ctxt) const final override
-  {
-    /* Update STATE based on summary_end_state.  */
-    call_details cd (get_call_details (state->m_region_model, ctxt));
-    call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
-    const program_state &summary_end_state = m_summary.get_state ();
-    return state->replay_call_summary (r, summary_end_state);
-  }
-
-  bool update_model (region_model *model,
-		     const exploded_edge *,
-		     region_model_context *ctxt) const final override
-  {
-    /* Update STATE based on summary_end_state.  */
-    call_details cd (get_call_details (model, ctxt));
-    call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
-    const program_state &summary_end_state = m_summary.get_state ();
-    model->replay_call_summary (r, *summary_end_state.m_region_model);
-    return true;
-  }
-
-  void print_desc (pretty_printer &pp) const final override
-  {
-    pp_string (&pp, m_summary.get_desc ().get ());
-  }
-
-private:
-  const function &m_called_fn;
-  call_summary &m_summary;
-  const extrinsic_state &m_ext_state;
-};
-
-/* Use PATH_CTXT to bifurcate, which when handled will add custom edges
-   for a replay of the various feasible summaries in CALLED_FN_DATA.  */
-
-exploded_node::on_stmt_flags
-exploded_node::replay_call_summaries (exploded_graph &eg,
-				      const supernode *snode,
-				      const gcall &call_stmt,
-				      program_state *state,
-				      path_context *path_ctxt,
-				      const function &called_fn,
-				      per_function_data &called_fn_data,
-				      region_model_context *ctxt)
-{
-  logger *logger = eg.get_logger ();
-  LOG_SCOPE (logger);
-
-  /* Each summary will call bifurcate on the PATH_CTXT.  */
-  for (auto summary : called_fn_data.m_summaries)
-    {
-      gcc_assert (summary);
-      replay_call_summary (eg, snode, call_stmt, state,
-			   path_ctxt, called_fn, *summary, ctxt);
-    }
-  path_ctxt->terminate_path ();
-
-  return on_stmt_flags ();
-}
-
-/* Use PATH_CTXT to bifurcate, which when handled will add a
-   custom edge for a replay of SUMMARY, if the summary's
-   conditions are feasible based on the current state.  */
-
-void
-exploded_node::replay_call_summary (exploded_graph &eg,
-				    const supernode *snode,
-				    const gcall &call_stmt,
-				    program_state *old_state,
-				    path_context *path_ctxt,
-				    const function &called_fn,
-				    call_summary &summary,
-				    region_model_context *ctxt)
-{
-  logger *logger = eg.get_logger ();
-  LOG_SCOPE (logger);
-  gcc_assert (snode);
-  gcc_assert (old_state);
-
-  if (logger)
-    logger->log ("using %s as summary for call to %qE from %qE",
-		 summary.get_desc ().get (),
-		 called_fn.decl,
-		 snode->get_function ()->decl);
-  const extrinsic_state &ext_state = eg.get_ext_state ();
-  const program_state &summary_end_state = summary.get_state ();
-  if (logger)
-    {
-      pretty_printer *pp = logger->get_printer ();
-
-      logger->start_log_line ();
-      pp_string (pp, "callsite state: ");
-      old_state->dump_to_pp (ext_state, true, false, pp);
-      logger->end_log_line ();
-
-      logger->start_log_line ();
-      pp_string (pp, "summary end state: ");
-      summary_end_state.dump_to_pp (ext_state, true, false, pp);
-      logger->end_log_line ();
-    }
-
-  program_state new_state (*old_state);
-
-  call_details cd (call_stmt, new_state.m_region_model, ctxt);
-  call_summary_replay r (cd, called_fn, summary, ext_state);
-
-  if (path_ctxt)
-    path_ctxt->bifurcate
-      (std::make_unique<call_summary_edge_info> (cd,
-						 called_fn,
-						 summary,
-						 ext_state));
-}
-
-
-/* Consider the effect of following superedge SUCC from this node.
-
-   Return true if it's feasible to follow the edge, or false
-   if it's infeasible.
-
-   Examples: if it's the "true" branch within
-   a CFG and we know the conditional is false, we know it's infeasible.
-   If it's one of multiple interprocedual "return" edges, then only
-   the edge back to the most recent callsite is feasible.
-
-   Update NEXT_STATE accordingly (e.g. to record that a condition was
-   true or false, or that the NULL-ness of a pointer has been checked,
-   pushing/popping stack frames, etc).
-
-   Update NEXT_POINT accordingly (updating the call string).  */
-
-bool
-exploded_node::on_edge (exploded_graph &eg,
-			const superedge *succ,
-			program_point *next_point,
-			program_state *next_state,
-			uncertainty_t *uncertainty)
-{
-  LOG_FUNC (eg.get_logger ());
-
-  if (!next_point->on_edge (eg, succ))
-    return false;
-
-  if (!next_state->on_edge (eg, this, succ, uncertainty))
-    return false;
-
-  return true;
-}
-
-/* Verify that the stack at LONGJMP_POINT is still valid, given a call
-   to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
-   called in must still be valid.
-
-   Caveat: this merely checks the call_strings in the points; it doesn't
-   detect the case where a frame returns and is then called again.  */
-
-static bool
-valid_longjmp_stack_p (const program_point &longjmp_point,
-		       const program_point &setjmp_point)
-{
-  const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
-  const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
-
-  if (cs_at_longjmp.length () < cs_at_setjmp.length ())
-    return false;
-
-  /* Check that the call strings match, up to the depth of the
-     setjmp point.  */
-  for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
-    if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
-      return false;
-
-  return true;
-}
-
-/* A pending_diagnostic subclass for complaining about bad longjmps,
-   where the enclosing function of the "setjmp" has returned (and thus
-   the stack frame no longer exists).  */
-
-class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
-{
-public:
-  stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
-		 const program_point &setjmp_point)
-  : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
-    m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
-  {}
-
-  int get_controlling_option () const final override
-  {
-    return OPT_Wanalyzer_stale_setjmp_buffer;
-  }
-
-  bool emit (diagnostic_emission_context &ctxt) final override
-  {
-    return ctxt.warn ("%qs called after enclosing function of %qs has returned",
-		      get_user_facing_name (m_longjmp_call),
-		      get_user_facing_name (m_setjmp_call));
-  }
-
-  const char *get_kind () const final override
-  { return "stale_jmp_buf"; }
-
-  bool operator== (const stale_jmp_buf &other) const
-  {
-    return (&m_setjmp_call == &other.m_setjmp_call
-	    && &m_longjmp_call == &other.m_longjmp_call);
-  }
+  bool operator== (const stale_jmp_buf &other) const
+  {
+    return (&m_setjmp_call == &other.m_setjmp_call
+	    && &m_longjmp_call == &other.m_longjmp_call);
+  }
 
   bool
-  maybe_add_custom_events_for_superedge (const exploded_edge &eedge,
-					 checker_path *emission_path)
+  maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
+				     checker_path *emission_path)
     final override
   {
     /* Detect exactly when the stack first becomes invalid,
@@ -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<stale_jmp_buf> (setjmp_call,
 						   longjmp_call,
-						   setjmp_point));
+						   point_before_setjmp));
       return;
     }
 
   gcc_assert (longjmp_point.get_stack_depth ()
-	      >= setjmp_point.get_stack_depth ());
+	      >= point_after_setjmp.get_stack_depth ());
 
   /* Update the state for use by the destination node.  */
 
@@ -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<throw_custom_edge> (cd, type, is_rethrow);
   throw_edge_info->update_model (model, nullptr, ctxt);
 
-  program_point after_throw_point = get_point ().get_next ();
-
   exploded_node *after_throw_enode
     = eg.get_or_create_node (after_throw_point, *new_state, this,
 			     /* Don't add to worklist; we process
@@ -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<return_event> (eedge,
-				       event_loc_info (m_dynamic_call.location,
-						       dest_point.get_fndecl (),
-						       dest_stack_depth)));
-  else
-    emission_path->add_event
-      (std::make_unique<call_event> (eedge,
-				     event_loc_info (m_dynamic_call.location,
-						     src_point.get_fndecl (),
-						     src_stack_depth)));
+void
+interprocedural_return::get_dot_attrs (const char *&/*out_style*/,
+				       const char *&out_color) const
+{
+  out_color = "green";
+}
+
+bool
+interprocedural_return::update_state (program_state *state,
+				      const exploded_edge *eedge,
+				      region_model_context *ctxt) const
+{
+  return update_model (state->m_region_model, eedge, ctxt);
+}
+
+bool
+interprocedural_return::update_model (region_model *model,
+				      const exploded_edge */*eedge*/,
+				      region_model_context *ctxt) const
+{
+  model->update_for_return_gcall (m_call_stmt, ctxt);
+  return true;
+}
+
+void
+interprocedural_return::add_events_to_path (checker_path *emission_path,
+					    const exploded_edge &eedge,
+					    pending_diagnostic &) const
+{
+  const program_point &dst_point = eedge.m_dest->get_point ();
+  emission_path->add_event
+    (std::make_unique<return_event>
+       (eedge,
+	event_loc_info (m_call_stmt.location,
+			dst_point.get_fndecl (),
+			dst_point.get_stack_depth ())));
 }
 
 /* class rewind_info_t : public custom_edge_info.  */
@@ -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 <enum point_kind> (i)),
-		   m_num_nodes[i]);
+  logger->log ("m_num_nodes: %i", m_num_nodes);
   logger->log ("m_node_reuse_count: %i", m_node_reuse_count);
   logger->log ("m_node_reuse_after_merge_count: %i",
 	       m_node_reuse_after_merge_count);
@@ -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 <enum point_kind> (i)),
-	       m_num_nodes[i]);
+  fprintf (out, "m_num_nodes: %i\n", m_num_nodes);
   fprintf (out, "m_node_reuse_count: %i\n", m_node_reuse_count);
   fprintf (out, "m_node_reuse_after_merge_count: %i\n",
 	   m_node_reuse_after_merge_count);
 
   if (m_num_supernodes > 0)
-    fprintf (out, "PK_AFTER_SUPERNODE nodes per supernode: %.2f\n",
-	     (float)m_num_nodes[PK_AFTER_SUPERNODE] / (float)m_num_supernodes);
+    fprintf (out, "enodes per supernode: %.2f\n",
+	     (float)m_num_nodes / (float)m_num_supernodes);
 }
 
 /* Return the total number of enodes recorded within this object.  */
@@ -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<json::array>
 strongly_connected_components::to_json () const
 {
   auto scc_arr = std::make_unique<json::array> ();
-  for (int i = 0; i < m_sg.num_nodes (); i++)
+  for (int i = 0; i < m_sg.m_nodes.length (); i++)
     scc_arr->append (std::make_unique<json::integer_number> (get_scc_id (i)));
   return scc_arr;
 }
@@ -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<tainted_args_function_custom_event>
@@ -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 <exploded_node *> 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<item> 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 <const gcall *> (stmt))
-    {
-      const gcall &call = *call_stmt;
-      /* Stop consolidating at calls to
-	 "__analyzer_dump_exploded_nodes", so they always appear at the
-	 start of an exploded_node.  */
-      if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
-				   1))
-	return true;
-
-      /* sm-signal.cc injects an additional custom eedge at "signal" calls
-	 from the registration enode to the handler enode, separate from the
-	 regular next state, which defeats the "detect state change" logic
-	 in process_node.  Work around this via special-casing, to ensure
-	 we split the enode immediately before any "signal" call.  */
-      if (is_special_named_call_p (call, "signal", 2, true))
-	return true;
-    }
-
-  /* If we had a PREV_STMT with an unknown location, and this stmt
-     has a known location, then if a state change happens here, it
-     could be consolidated into PREV_STMT, giving us an event with
-     no location.  Ensure that STMT gets its own exploded_node to
-     avoid this.  */
-  if (get_pure_location (prev_stmt->location) == UNKNOWN_LOCATION
-      && get_pure_location (stmt->location) != UNKNOWN_LOCATION)
-    return true;
-
-  return false;
-}
-
-/* Return true if OLD_STATE and NEW_STATE are sufficiently different that
-   we should split enodes and create an exploded_edge separating them
-   (which makes it easier to identify state changes of intereset when
-   constructing checker_paths).  */
-
-static bool
-state_change_requires_new_enode_p (const program_state &old_state,
-				   const program_state &new_state)
-{
-  /* Changes in dynamic extents signify creations of heap/alloca regions
-     and resizings of heap regions; likely to be of interest in
-     diagnostic paths.  */
-  if (old_state.m_region_model->get_dynamic_extents ()
-      != new_state.m_region_model->get_dynamic_extents ())
-    return true;
-
-  /* Changes in sm-state are of interest.  */
-  int sm_idx;
-  sm_state_map *smap;
-  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
-    {
-      const sm_state_map *old_smap = old_state.m_checker_states[sm_idx];
-      const sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
-      if (*old_smap != *new_smap)
-	return true;
-    }
-
-  return false;
-}
-
-/* Create enodes and eedges for the function calls that doesn't have an
-   underlying call superedge.
-
-   Such case occurs when GCC's middle end didn't know which function to
-   call but the analyzer does (with the help of current state).
-
-   Some example such calls are dynamically dispatched calls to virtual
-   functions or calls that happen via function pointer.  */
-
-bool
-exploded_graph::maybe_create_dynamic_call (const gcall &call,
-					   tree fn_decl,
-					   exploded_node *node,
-					   program_state next_state,
-					   program_point &next_point,
-					   uncertainty_t *uncertainty,
-					   logger *logger)
-{
-  LOG_FUNC (logger);
-
-  const program_point *this_point = &node->get_point ();
-  function *fun = DECL_STRUCT_FUNCTION (fn_decl);
-  if (fun)
-    {
-      const supergraph &sg = this->get_supergraph ();
-      supernode *sn_entry = sg.get_node_for_function_entry (*fun);
-      supernode *sn_exit = sg.get_node_for_function_exit (*fun);
-
-      program_point new_point
-	= program_point::before_supernode (sn_entry,
-					   nullptr,
-					   this_point->get_call_string ());
-
-      new_point.push_to_call_stack (sn_exit,
-				    next_point.get_supernode());
-
-      /* Impose a maximum recursion depth and don't analyze paths
-	 that exceed it further.
-	 This is something of a blunt workaround, but it only
-	 applies to recursion (and mutual recursion), not to
-	 general call stacks.  */
-      if (new_point.get_call_string ().calc_recursion_depth ()
-	  > param_analyzer_max_recursion_depth)
-      {
-	if (logger)
-	  logger->log ("rejecting call edge: recursion limit exceeded");
-	return false;
-      }
-
-      next_state.push_call (*this, node, call, uncertainty);
-
-      if (next_state.m_valid)
-	{
-	  if (logger)
-	    logger->log ("Discovered call to %s [SN: %i -> SN: %i]",
-			 function_name(fun),
-			 this_point->get_supernode ()->m_index,
-			 sn_entry->m_index);
-
-	  exploded_node *enode = get_or_create_node (new_point,
-						     next_state,
-						     node);
-	  if (enode)
-	    add_edge (node,enode, nullptr,
-		      false, /* No work is done by the call itself.  */
-		      std::make_unique<dynamic_call_info_t> (call));
-	  return true;
-	}
-    }
-  return false;
-}
-
-/* Subclass of path_context for use within exploded_graph::process_node,
-   so that we can split states e.g. at "realloc" calls.  */
-
-class impl_path_context : public path_context
-{
-public:
-  impl_path_context (const program_state *cur_state,
-		     logger *logger)
-  : m_cur_state (cur_state),
-    m_logger (logger),
-    m_terminate_path (false)
-  {
-  }
-
-  bool bifurcation_p () const
-  {
-    return m_custom_eedge_infos.length () > 0;
-  }
-
-  const program_state &get_state_at_bifurcation () const
-  {
-    gcc_assert (m_state_at_bifurcation);
-    return *m_state_at_bifurcation;
-  }
-
-  void
-  bifurcate (std::unique_ptr<custom_edge_info> info) final override
-  {
-    if (m_logger)
-      m_logger->log ("bifurcating path");
-
-    if (m_state_at_bifurcation)
-      /* Verify that the state at bifurcation is consistent when we
-	 split into multiple out-edges.  */
-      gcc_assert (*m_state_at_bifurcation == *m_cur_state);
-    else
-      /* Take a copy of the cur_state at the moment when bifurcation
-	 happens.  */
-      m_state_at_bifurcation
-	= std::unique_ptr<program_state> (new program_state (*m_cur_state));
-
-    /* Take ownership of INFO.  */
-    m_custom_eedge_infos.safe_push (info.release ());
-  }
-
-  void terminate_path () final override
-  {
-    if (m_logger)
-      m_logger->log ("terminating path");
-    m_terminate_path = true;
-  }
-
-  bool terminate_path_p () const final override
-  {
-    return m_terminate_path;
-  }
-
-  const vec<custom_edge_info *> & get_custom_eedge_infos ()
-  {
-    return m_custom_eedge_infos;
-  }
-
-private:
-  const program_state *m_cur_state;
-
-  logger *m_logger;
-
-  /* Lazily-created copy of the state before the split.  */
-  std::unique_ptr<program_state> m_state_at_bifurcation;
-
-  auto_vec <custom_edge_info *> m_custom_eedge_infos;
-
-  bool m_terminate_path;
-};
-
-/* A subclass of pending_diagnostic for complaining about jumps through NULL
-   function pointers.  */
-
-class jump_through_null : public pending_diagnostic_subclass<jump_through_null>
-{
-public:
-  jump_through_null (const gcall &call)
-  : m_call (call)
-  {}
-
-  const char *get_kind () const final override
-  {
-    return "jump_through_null";
-  }
-
-  bool operator== (const jump_through_null &other) const
-  {
-    return &m_call == &other.m_call;
-  }
-
-  int get_controlling_option () const final override
-  {
-    return OPT_Wanalyzer_jump_through_null;
-  }
-
-  bool emit (diagnostic_emission_context &ctxt) final override
-  {
-    return ctxt.warn ("jump through null pointer");
-  }
-
-  bool describe_final_event (pretty_printer &pp,
-			     const evdesc::final_event &) final override
-  {
-    pp_string (&pp, "jump through null pointer here");
-    return true;
-  }
-
-private:
-  const gcall &m_call;
-};
-
 /* The core of exploded_graph::process_worklist (the main analysis loop),
    handling one node in the worklist.
 
@@ -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<interprocedural_return> (top_of_stack.get_call_stmt ());
+
+	  const program_state &src_state (node->get_state ());
+	  program_state dst_state (src_state);
+	  uncertainty_t uncertainty;
+	  impl_region_model_context ctxt (*this, node,
+					  &src_state, &dst_state, &uncertainty,
+					  nullptr,
+					  nullptr);
+	  edge_info->update_state (&dst_state, nullptr, &ctxt);
+
+	  program_state::detect_leaks (src_state, dst_state,
+				       nullptr, get_ext_state (),
+				       &ctxt);
+
+	  if (exploded_node *next
+	      = get_or_create_node (dst_point, dst_state, node))
+	    add_edge (node, next, nullptr, false,
+		      std::move (edge_info));
+	}
+      else
+	{
+	  /* End of top-level of analysis for this function.
+	     Detect leaks, and potentially create a function summary.  */
+	  node->detect_leaks (*this);
 
-	    if (stmt_idx > point.get_stmt_idx ())
-	      if (stmt_requires_new_enode_p (stmt, prev_stmt))
+	  if (flag_analyzer_call_summaries
+	      && point.get_call_string ().empty_p ())
+	    {
+	      /* TODO: create function summary
+		 There can be more than one; each corresponds to a different
+		 final enode in the function.  */
+	      if (logger)
 		{
-		  stmt_idx--;
-		  break;
+		  pretty_printer *pp = logger->get_printer ();
+		  logger->start_log_line ();
+		  logger->log_partial
+		    ("would create function summary for %qE; state: ",
+		     point.get_fndecl ());
+		  state.dump_to_pp (m_ext_state, true, false, pp);
+		  logger->end_log_line ();
 		}
-	    prev_stmt = stmt;
-
-	    program_state old_state (next_state);
-
-	    /* Process the stmt.  */
-	    exploded_node::on_stmt_flags flags
-	      = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty,
-			       &could_have_done_work, &path_ctxt);
-	    node->m_num_processed_stmts++;
-
-	    /* If flags.m_terminate_path, stop analyzing; any nodes/edges
-	       will have been added by on_stmt (e.g. for handling longjmp).  */
-	    if (flags.m_terminate_path)
-	      return;
-
-	    if (next_state.m_region_model)
-	      {
-		impl_region_model_context ctxt (*this, node,
-						&old_state, &next_state,
-						&uncertainty, nullptr, stmt);
-		program_state::detect_leaks (old_state, next_state, nullptr,
-					     get_ext_state (), &ctxt);
-	      }
-
-	    unsigned next_idx = stmt_idx + 1;
-	    program_point next_point
-	      = (next_idx < point.get_supernode ()->m_stmts.length ()
-		 ? program_point::before_stmt (point.get_supernode (), next_idx,
-					       point.get_call_string ())
-		 : program_point::after_supernode (point.get_supernode (),
-						   point.get_call_string ()));
-	    next_state = next_state.prune_for_point (*this, next_point, node,
-						     &uncertainty);
-
-	    if (flag_analyzer_fine_grained
-		|| state_change_requires_new_enode_p (old_state, next_state)
-		|| path_ctxt.bifurcation_p ()
-		|| path_ctxt.terminate_path_p ())
-	      {
-		program_point split_point
-		  = program_point::before_stmt (point.get_supernode (),
-						stmt_idx,
-						point.get_call_string ());
-		if (split_point != node->get_point ())
-		  {
-		    /* If we're not at the start of NODE, split the enode at
-		       this stmt, so we have:
-			 node -> split_enode
-		       so that when split_enode is processed the next edge
-		       we add will be:
-			 split_enode -> next
-		       and any state change will effectively occur on that
-		       latter edge, and split_enode will contain just stmt.  */
-		    if (logger)
-		      logger->log ("getting split_enode");
-		    exploded_node *split_enode
-		      = get_or_create_node (split_point, old_state, node);
-		    if (!split_enode)
-		      return;
-		    /* "stmt" will be reprocessed when split_enode is
-		       processed.  */
-		    node->m_num_processed_stmts--;
-		    if (logger)
-		      logger->log ("creating edge to split_enode");
-		    add_edge (node, split_enode, nullptr, could_have_done_work);
-		    return;
-		  }
-		else
-		  /* If we're at the start of NODE, stop iterating,
-		     so that an edge will be created from NODE to
-		     (next_point, next_state) below. */
-		  break;
-	      }
-	  }
-	unsigned next_idx = stmt_idx + 1;
-	program_point next_point
-	  = (next_idx < point.get_supernode ()->m_stmts.length ()
-	     ? program_point::before_stmt (point.get_supernode (), next_idx,
-					   point.get_call_string ())
-	     : program_point::after_supernode (point.get_supernode (),
-					       point.get_call_string ()));
-	if (path_ctxt.terminate_path_p ())
-	  {
-	    if (logger)
-	      logger->log ("not adding node: terminating path");
-	  }
-	else
-	  {
-	    exploded_node *next
-	      = get_or_create_node (next_point, next_state, node);
-	    if (next)
-	      add_edge (node, next, nullptr, could_have_done_work);
-	  }
-
-	/* If we have custom edge infos, "bifurcate" the state
-	   accordingly, potentially creating a new state/enode/eedge
-	   instances.  For example, to handle a "realloc" call, we
-	   might split into 3 states, for the "failure",
-	   "resizing in place", and "moving to a new buffer" cases.  */
-	for (auto edge_info_iter : path_ctxt.get_custom_eedge_infos ())
-	  {
-	    /* Take ownership of the edge infos from the path_ctxt.  */
-	    std::unique_ptr<custom_edge_info> edge_info (edge_info_iter);
-	    if (logger)
-	      {
-		logger->start_log_line ();
-		logger->log_partial ("bifurcating for edge: ");
-		edge_info->print (logger->get_printer ());
-		logger->end_log_line ();
-	      }
-	    program_state bifurcated_new_state
-	      (path_ctxt.get_state_at_bifurcation ());
-
-	    /* Apply edge_info to state.  */
-	    impl_region_model_context
-	      bifurcation_ctxt (*this,
-				node, // enode_for_diag
-				&path_ctxt.get_state_at_bifurcation (),
-				&bifurcated_new_state,
-				nullptr, // uncertainty_t *uncertainty
-				nullptr, // path_context *path_ctxt
-				stmt);
-	    if (edge_info->update_state (&bifurcated_new_state,
-					 nullptr, /* no exploded_edge yet.  */
-					 &bifurcation_ctxt))
-	      {
-		if (exploded_node *next2
-		    = edge_info->create_enode
-			(*this,
-			 next_point,
-			 std::move (bifurcated_new_state),
-			 node,
-			 &bifurcation_ctxt))
-		  {
-		    add_edge (node, next2, nullptr,
-			      true /* assume that work could be done */,
-			      std::move (edge_info));
-		  }
-	      }
-	    else
-	      {
-		if (logger)
-		  logger->log ("infeasible state, not adding node");
-	      }
-	  }
-      }
-      break;
-    case PK_AFTER_SUPERNODE:
-      {
-	bool found_a_superedge = false;
-	bool is_an_exit_block = false;
-	/* If this is an EXIT BB, detect leaks, and potentially
-	   create a function summary.  */
-	if (point.get_supernode ()->return_p ())
-	  {
-	    is_an_exit_block = true;
-	    node->detect_leaks (*this);
-	    if (flag_analyzer_call_summaries
-		&& point.get_call_string ().empty_p ())
-	      {
-		/* TODO: create function summary
-		   There can be more than one; each corresponds to a different
-		   final enode in the function.  */
-		if (logger)
-		  {
-		    pretty_printer *pp = logger->get_printer ();
-		    logger->start_log_line ();
-		    logger->log_partial
-		      ("would create function summary for %qE; state: ",
-		       point.get_fndecl ());
-		    state.dump_to_pp (m_ext_state, true, false, pp);
-		    logger->end_log_line ();
-		  }
-		per_function_data *per_fn_data
-		  = get_or_create_per_function_data (point.get_function ());
-		per_fn_data->add_call_summary (node);
-	      }
-	  }
-	/* Traverse into successors of the supernode.  */
-	int i;
-	superedge *succ;
-	FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
-	  {
-	    found_a_superedge = true;
-	    if (logger)
-	      {
-		label_text succ_desc (succ->get_description (false));
-		logger->log ("considering SN: %i -> SN: %i (%s)",
-			     succ->m_src->m_index, succ->m_dest->m_index,
-			     succ_desc.get ());
-	      }
-
-	    program_point next_point
-	      = program_point::before_supernode (succ->m_dest, succ,
-						 point.get_call_string ());
-	    program_state next_state (state);
-	    uncertainty_t uncertainty;
-
-	    /* Make use the current state and try to discover and analyse
-	       indirect function calls (a call that doesn't have an underlying
-	       cgraph edge representing call).
-
-	       Some examples of such calls are virtual function calls
-	       and calls that happen via a function pointer.  */
-	    if (succ->m_kind == SUPEREDGE_INTRAPROCEDURAL_CALL
-		&& !(succ->get_any_callgraph_edge ()))
-	      {
-		const gcall &call
-		  = *point.get_supernode ()->get_final_call ();
-
-		impl_region_model_context ctxt (*this,
-						node,
-						&state,
-						&next_state,
-						&uncertainty,
-						nullptr,
-						point.get_stmt());
-
-		region_model *model = state.m_region_model;
-		bool call_discovered = false;
-
-		if (tree fn_decl = model->get_fndecl_for_call (call, &ctxt))
-		  call_discovered = maybe_create_dynamic_call (call,
-							       fn_decl,
-							       node,
-							       next_state,
-							       next_point,
-							       &uncertainty,
-							       logger);
-		if (!call_discovered)
-		  {
-		    /* Check for jump through nullptr.  */
-		    if (tree fn_ptr = gimple_call_fn (&call))
-		      {
-			const svalue *fn_ptr_sval
-			  = model->get_rvalue (fn_ptr, &ctxt);
-			if (fn_ptr_sval->all_zeroes_p ())
-			  ctxt.warn
-			    (std::make_unique<jump_through_null> (call));
-		      }
-
-		    /* An unknown function or a special function was called
-		       at this point, in such case, don't terminate the
-		       analysis of the current function.
-
-		       The analyzer handles calls to such functions while
-		       analysing the stmt itself, so the function call
-		       must have been handled by the anlyzer till now.  */
-		    exploded_node *next
-		      = get_or_create_node (next_point,
-					    next_state,
-					    node);
-		    if (next)
-		      add_edge (node, next, succ,
-				true /* assume that work is done */);
-		  }
-	      }
-
-	    /* Ignore CFG edges in the sgraph flagged with EH whilst
-	       we're exploring the egraph.
-	       We only use these sedges in special-case logic for
-	       dealing with exception-handling.  */
-	    if (auto cfg_sedge = succ->dyn_cast_cfg_superedge ())
-	      if (cfg_sedge->get_flags () & EDGE_EH)
-		{
-		  if (logger)
-		    logger->log ("rejecting EH edge");
-		  continue;
-	      }
+	      per_function_data *per_fn_data
+		= get_or_create_per_function_data (point.get_function ());
+	      per_fn_data->add_call_summary (node);
+	    }
+	}
 
-	    if (!node->on_edge (*this, succ, &next_point, &next_state,
-				&uncertainty))
-	      {
-		if (logger)
-		  logger->log ("skipping impossible edge to SN: %i",
-			       succ->m_dest->m_index);
-		continue;
-	      }
-	    exploded_node *next = get_or_create_node (next_point, next_state,
-						      node);
-	    if (next)
-	      {
-		add_edge (node, next, succ, false);
+      return;
+    }
 
-		/* We might have a function entrypoint.  */
-		detect_infinite_recursion (next);
-	      }
-	  }
+  /* Traverse into successors of the supernode.  */
+  int i;
+  superedge *succ;
+  FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
+    {
+      if (logger)
+	{
+	  label_text succ_desc (succ->get_description (false));
+	  logger->log ("considering SN: %i -> SN: %i (%s)",
+		       succ->m_src->m_id, succ->m_dest->m_id,
+		       succ_desc.get ());
+	}
 
-	/* Return from the calls which doesn't have a return superedge.
-	   Such case occurs when GCC's middle end didn't knew which function to
-	   call but analyzer did.  */
-	if ((is_an_exit_block && !found_a_superedge)
-	    && (!point.get_call_string ().empty_p ()))
-	  {
-	    const call_string &cs = point.get_call_string ();
-	    program_point next_point
-	      = program_point::before_supernode (cs.get_caller_node (),
-						 nullptr,
-						 cs);
-	    program_state next_state (state);
-	    uncertainty_t uncertainty;
+      program_point next_point (succ->m_dest, point.get_call_string ());
+      program_state next_state (state);
+      uncertainty_t uncertainty;
 
-	    const gcall *call
-	      = next_point.get_supernode ()->get_returning_call ();
+      /* Find the outcome(s) of any operation on the edge.  */
+      operation_context op_ctxt (*this, *node, *succ);
 
-	    if (call)
-	      next_state.returning_call (*this, node, *call, &uncertainty);
+      /* Skip EH edges.  */
+      if (auto cfg_edge = succ->get_any_cfg_edge ())
+	if (cfg_edge->flags & EDGE_EH)
+	  continue;
 
-	    if (next_state.m_valid)
-	      {
-		next_point.pop_from_call_stack ();
-		exploded_node *enode = get_or_create_node (next_point,
-							   next_state,
-							   node);
-		if (enode)
-		  add_edge (node, enode, nullptr, false,
-			    std::make_unique<dynamic_call_info_t> (*call, true));
-	      }
-	  }
-      }
-      break;
+      if (auto op = succ->get_op ())
+	op->execute (op_ctxt);
+      else
+	{
+	  /* No-op.
+	     Unconditional goto to the dst point, which
+	     must be in same function.
+	     The supernode changes, but the callstring and
+	     state do not change.  */
+	  if (logger)
+	    logger->log ("handling no-op edge");
+	  auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+	  if (exploded_node *next
+	      = get_or_create_node (dst_point,
+				    node->get_state (),
+				    node))
+	    add_edge (node, next, succ, false);
+	}
     }
 }
 
@@ -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<unsigned> enodes_per_supernode (m_sg.num_nodes ());
-  for (int i = 0; i < m_sg.num_nodes (); i++)
+  auto_vec<unsigned> enodes_per_supernode (m_sg.m_nodes.length ());
+  for (int i = 0; i < m_sg.m_nodes.length (); i++)
     enodes_per_supernode.quick_push (0);
   int i;
   exploded_node *enode;
@@ -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<unsigned> excess_enodes_per_supernode (m_sg.num_nodes ());
-  for (int i = 0; i < m_sg.num_nodes (); i++)
+  auto_vec<unsigned> excess_enodes_per_supernode (m_sg.m_nodes.length ());
+  for (int i = 0; i < m_sg.m_nodes.length (); i++)
     excess_enodes_per_supernode.quick_push (0);
   for (point_map_t::iterator iter = m_per_point_data.begin ();
        iter != m_per_point_data.end (); ++iter)
@@ -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<int> 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<feasibility_problem> (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 <const gassign *> (stmt))
-    m_model.on_assignment (assign, nullptr);
-  else if (const gasm *asm_stmt = dyn_cast <const gasm *> (stmt))
-    m_model.on_asm_stmt (asm_stmt, nullptr);
-  else if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      bool unknown_side_effects = m_model.on_call_pre (*call, nullptr);
-      m_model.on_call_post (*call, unknown_side_effects, nullptr);
-    }
-  else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
-    m_model.on_return (return_, nullptr);
+  return true;
 }
 
 /* Dump this object to PP.  */
@@ -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<const gimple *> seen;
   FOR_EACH_VEC_ELT (m_nodes, i, enode)
     {
-      if (enode->get_point ().get_kind () != PK_BEFORE_STMT)
+      const supernode *snode = enode->get_supernode ();
+      if (!snode)
+	continue;
+      if (snode->m_succs.length () != 1)
+	continue;
+      const superedge *sedge = snode->m_succs[0];
+      if (!sedge->get_op ())
+	continue;
+      const call_and_return_op *op
+	= sedge->get_op ()->dyn_cast_call_and_return_op ();
+      if (!op)
 	continue;
+      const gcall &call = op->get_gcall ();
+      if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes", 1))
+	{
+	  if (seen.contains (&call))
+	    continue;
 
-      if (const gimple *stmt = enode->get_stmt ())
-	if (const gcall *call = dyn_cast <const gcall *> (stmt))
-	  if (is_special_named_call_p (*call, "__analyzer_dump_exploded_nodes",
-				       1))
+	  auto_vec<exploded_node *> processed_enodes;
+	  auto_vec<exploded_node *> merger_enodes;
+	  auto_vec<exploded_node *> worklist_enodes;
+	  /* This is O(N^2).  */
+	  unsigned j;
+	  exploded_node *other_enode;
+	  FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
 	    {
-	      if (seen.contains (stmt))
-		continue;
-
-	      auto_vec<exploded_node *> processed_enodes;
-	      auto_vec<exploded_node *> merger_enodes;
-	      auto_vec<exploded_node *> worklist_enodes;
-	      /* This is O(N^2).  */
-	      unsigned j;
-	      exploded_node *other_enode;
-	      FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
-		{
-		  if (other_enode->get_point ().get_kind () != PK_BEFORE_STMT)
-		    continue;
-		  if (other_enode->get_stmt () == stmt)
-		    switch (other_enode->get_status ())
-		      {
-		      default:
-			gcc_unreachable ();
-		      case exploded_node::status::worklist:
-			worklist_enodes.safe_push (other_enode);
-			break;
-		      case exploded_node::status::processed:
-			processed_enodes.safe_push (other_enode);
-			break;
-		      case exploded_node::status::merger:
-			merger_enodes.safe_push (other_enode);
-			break;
-		      }
-		}
+	      if (other_enode->get_supernode () == snode)
+		switch (other_enode->get_status ())
+		  {
+		  default:
+		    gcc_unreachable ();
+		  case exploded_node::status::worklist:
+		    worklist_enodes.safe_push (other_enode);
+		    break;
+		  case exploded_node::status::processed:
+		    processed_enodes.safe_push (other_enode);
+		    break;
+		  case exploded_node::status::merger:
+		    merger_enodes.safe_push (other_enode);
+		    break;
+		  }
+	    }
 
-	      pretty_printer pp;
-	      pp_character (&pp, '[');
-	      print_enode_indices (&pp, processed_enodes);
-	      if (merger_enodes.length () > 0)
-		{
-		  pp_string (&pp, "] merger(s): [");
-		  print_enode_indices (&pp, merger_enodes);
-		}
-	      if (worklist_enodes.length () > 0)
-		{
-		  pp_string (&pp, "] worklist: [");
-		  print_enode_indices (&pp, worklist_enodes);
-		}
-	      pp_character (&pp, ']');
-
-	      warning_n (stmt->location, 0, processed_enodes.length (),
-			 "%i processed enode: %s",
-			 "%i processed enodes: %s",
-			 processed_enodes.length (), pp_formatted_text (&pp));
-	      seen.add (stmt);
-
-	      /* If the argument is non-zero, then print all of the states
-		 of the various enodes.  */
-	      tree t_arg = fold (gimple_call_arg (call, 0));
-	      if (TREE_CODE (t_arg) != INTEGER_CST)
-		{
-		  error_at (call->location,
-			    "integer constant required for arg 1");
-		  return;
-		}
-	      int i_arg = TREE_INT_CST_LOW (t_arg);
-	      if (i_arg)
+	  pretty_printer pp;
+	  pp_character (&pp, '[');
+	  print_enode_indices (&pp, processed_enodes);
+	  if (merger_enodes.length () > 0)
+	    {
+	      pp_string (&pp, "] merger(s): [");
+	      print_enode_indices (&pp, merger_enodes);
+	    }
+	  if (worklist_enodes.length () > 0)
+	    {
+	      pp_string (&pp, "] worklist: [");
+	      print_enode_indices (&pp, worklist_enodes);
+	    }
+	  pp_character (&pp, ']');
+
+	  warning_n (call.location, 0, processed_enodes.length (),
+		     "%i processed enode: %s",
+		     "%i processed enodes: %s",
+		     processed_enodes.length (), pp_formatted_text (&pp));
+	  seen.add (&call);
+
+	  /* If the argument is non-zero, then print all of the states
+	     of the various enodes.  */
+	  tree t_arg = fold (gimple_call_arg (&call, 0));
+	  if (TREE_CODE (t_arg) != INTEGER_CST)
+	    {
+	      error_at (snode->m_loc,
+			"integer constant required for arg 1");
+	      return;
+	    }
+	  int i_arg = TREE_INT_CST_LOW (t_arg);
+	  if (i_arg)
+	    {
+	      exploded_node *other_enode;
+	      FOR_EACH_VEC_ELT (processed_enodes, j, other_enode)
 		{
-		  exploded_node *other_enode;
-		  FOR_EACH_VEC_ELT (processed_enodes, j, other_enode)
-		    {
-		      fprintf (stderr, "%i of %i: EN %i:\n",
-			       j + 1, processed_enodes.length (),
-			       other_enode->m_index);
-		      other_enode->dump_succs_and_preds (stderr);
-		      /* Dump state.  */
-		      other_enode->get_state ().dump (m_ext_state, false);
-		    }
+		  fprintf (stderr, "%i of %i: EN %i:\n",
+			   j + 1, processed_enodes.length (),
+			   other_enode->m_index);
+		  other_enode->dump_succs_and_preds (stderr);
+		  /* Dump state.  */
+		  other_enode->get_state ().dump (m_ext_state, false);
 		}
 	    }
+	}
     }
 }
 
@@ -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<viz_callgraph_traits>
-{
-  friend class viz_callgraph;
-
-public:
-  viz_callgraph_node (function *fun, int index)
-  : m_fun (fun), m_index (index), m_num_supernodes (0), m_num_superedges (0)
-  {
-    gcc_assert (fun);
-  }
-
-  void dump_dot (graphviz_out *gv, const dump_args_t &args) const final override
-  {
-    pretty_printer *pp = gv->get_pp ();
-
-    dump_dot_id (pp);
-    pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
-	       "lightgrey");
-    pp_write_text_to_stream (pp);
-
-    pp_printf (pp, "VCG: %i: %s", m_index, function_name (m_fun));
-    pp_newline (pp);
-
-    pp_printf (pp, "supernodes: %i\n", m_num_supernodes);
-    pp_newline (pp);
-
-    pp_printf (pp, "superedges: %i\n", m_num_superedges);
-    pp_newline (pp);
-
-    if (args.m_eg)
-      {
-	unsigned i;
-	exploded_node *enode;
-	unsigned num_enodes = 0;
-	FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
-	  {
-	    if (enode->get_point ().get_function () == m_fun)
-	      num_enodes++;
-	  }
-	pp_printf (pp, "enodes: %i\n", num_enodes);
-	pp_newline (pp);
-
-	// TODO: also show the per-callstring breakdown
-	const exploded_graph::call_string_data_map_t *per_cs_data
-	  = args.m_eg->get_per_call_string_data ();
-	for (exploded_graph::call_string_data_map_t::iterator iter
-	       = per_cs_data->begin ();
-	     iter != per_cs_data->end ();
-	     ++iter)
-	  {
-	    const call_string *cs = (*iter).first;
-	    //per_call_string_data *data = (*iter).second;
-	    num_enodes = 0;
-	    FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
-	      {
-		if (enode->get_point ().get_function () == m_fun
-		    && &enode->get_point ().get_call_string () == cs)
-		  num_enodes++;
-	      }
-	    if (num_enodes > 0)
-	      {
-		cs->print (pp);
-		pp_printf (pp, ": %i\n", num_enodes);
-	      }
-	  }
-
-	/* Show any summaries.  */
-	per_function_data *data = args.m_eg->get_per_function_data (m_fun);
-	if (data)
-	  {
-	    pp_newline (pp);
-	    pp_printf (pp, "summaries: %i\n", data->m_summaries.length ());
-	    for (auto summary : data->m_summaries)
-	      {
-		pp_printf (pp, "\nsummary: %s:\n", summary->get_desc ().get ());
-		const extrinsic_state &ext_state = args.m_eg->get_ext_state ();
-		const program_state &state = summary->get_state ();
-		state.dump_to_pp (ext_state, false, true, pp);
-		pp_newline (pp);
-	      }
-	  }
-      }
-
-    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
-    pp_string (pp, "\"];\n\n");
-    pp_flush (pp);
-  }
-
-  void dump_dot_id (pretty_printer *pp) const
-  {
-    pp_printf (pp, "vcg_%i", m_index);
-  }
-
-private:
-  function *m_fun;
-  int m_index;
-  int m_num_supernodes;
-  int m_num_superedges;
-};
-
-/* Subclass of dedge representing a callgraph edge.  */
-
-class viz_callgraph_edge : public dedge<viz_callgraph_traits>
-{
-public:
-  viz_callgraph_edge (viz_callgraph_node *src, viz_callgraph_node *dest)
-  : dedge<viz_callgraph_traits> (src, dest)
-  {}
-
-  void dump_dot (graphviz_out *gv, const dump_args_t &) const
-    final override
-  {
-    pretty_printer *pp = gv->get_pp ();
-
-    const char *style = "\"solid,bold\"";
-    const char *color = "black";
-    int weight = 10;
-    const char *constraint = "true";
-
-    m_src->dump_dot_id (pp);
-    pp_string (pp, " -> ");
-    m_dest->dump_dot_id (pp);
-    pp_printf (pp,
-	       (" [style=%s, color=%s, weight=%d, constraint=%s,"
-		" headlabel=\""),
-	       style, color, weight, constraint);
-    pp_printf (pp, "\"];\n");
-  }
-};
-
-/* Subclass of digraph representing the callgraph.  */
-
-class viz_callgraph : public digraph<viz_callgraph_traits>
-{
-public:
-  viz_callgraph (const supergraph &sg);
-
-  viz_callgraph_node *get_vcg_node_for_function (function *fun)
-  {
-    return *m_map.get (fun);
-  }
-
-  viz_callgraph_node *get_vcg_node_for_snode (supernode *snode)
-  {
-    return get_vcg_node_for_function (snode->m_fun);
-  }
-
-private:
-  hash_map<function *, viz_callgraph_node *> m_map;
-};
-
-/* Placeholder subclass of cluster.  */
-
-class viz_callgraph_cluster : public cluster<viz_callgraph_traits>
-{
-};
-
-/* viz_callgraph's ctor.  */
-
-viz_callgraph::viz_callgraph (const supergraph &sg)
-{
-  cgraph_node *node;
-  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
-    {
-      function *fun = node->get_fun ();
-      viz_callgraph_node *vcg_node
-	= new viz_callgraph_node (fun, m_nodes.length ());
-      m_map.put (fun, vcg_node);
-      add_node (vcg_node);
-    }
-
-  unsigned i;
-  superedge *sedge;
-  FOR_EACH_VEC_ELT (sg.m_edges, i, sedge)
-    {
-      viz_callgraph_node *vcg_src = get_vcg_node_for_snode (sedge->m_src);
-      if (vcg_src->m_fun)
-	get_vcg_node_for_function (vcg_src->m_fun)->m_num_superedges++;
-      if (sedge->dyn_cast_call_superedge ())
-	{
-	  viz_callgraph_node *vcg_dest = get_vcg_node_for_snode (sedge->m_dest);
-	  viz_callgraph_edge *vcg_edge
-	    = new viz_callgraph_edge (vcg_src, vcg_dest);
-	  add_edge (vcg_edge);
-	}
-    }
-
-  supernode *snode;
-  FOR_EACH_VEC_ELT (sg.m_nodes, i, snode)
-    {
-      if (snode->m_fun)
-	get_vcg_node_for_function (snode->m_fun)->m_num_supernodes++;
-    }
-}
-
-/* Dump the callgraph to FILENAME.  */
-
-static void
-dump_callgraph (const supergraph &sg, const char *filename,
-		const exploded_graph *eg)
-{
-  FILE *outf = fopen (filename, "w");
-  if (!outf)
-    return;
-
-  // TODO
-  viz_callgraph vcg (sg);
-  vcg.dump_dot (filename, nullptr, viz_callgraph_traits::dump_args_t (eg));
-
-  fclose (outf);
-}
-
-/* Dump the callgraph to "<srcfile>.callgraph.dot".  */
-
-static void
-dump_callgraph (const supergraph &sg, const exploded_graph *eg)
-{
-  auto_timevar tv (TV_ANALYZER_DUMP);
-  char *filename = concat (dump_base_name, ".callgraph.dot", nullptr);
-  dump_callgraph (sg, filename, eg);
-  free (filename);
-}
-
 /* Subclass of dot_annotator for implementing
-   DUMP_BASE_NAME.supergraph-eg.dot, a post-analysis dump of the supergraph.
+   DUMP_BASE_NAME.supergraph.N.eg.dot, a post-analysis dump of the supergraph.
 
    Annotate the supergraph nodes by printing the exploded nodes in concise
-   form within them, next to their pertinent statements where appropriate,
-   colorizing the exploded nodes based on sm-state.
+   form within them, colorizing the exploded nodes based on sm-state.
    Also show saved diagnostics within the exploded nodes, giving information
    on whether they were feasible, and, if infeasible, where the problem
    was.  */
@@ -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 <exploded_node *> ());
+    /* Avoid O(N^2) by prepopulating m_enodes_per_snode_id.  */
+    for (int i = 0; i < eg.get_supergraph ().m_nodes.length (); ++i)
+      m_enodes_per_snode_id.push_back (std::vector<exploded_node *> ());
     exploded_node *enode;
+    unsigned i;
     FOR_EACH_VEC_ELT (m_eg.m_nodes, i, enode)
       if (enode->get_supernode ())
-	m_enodes_per_snodes[enode->get_supernode ()->m_index]->safe_push (enode);
+	m_enodes_per_snode_id[enode->get_supernode ()->m_id].push_back (enode);
   }
 
-  /* Show exploded nodes for BEFORE_SUPERNODE points before N.  */
-  bool add_node_annotations (graphviz_out *gv, const supernode &n,
-			     bool within_table)
+  /* Show exploded nodes for N.  */
+  void add_node_annotations (graphviz_out *gv, const supernode &n)
     const final override
   {
-    if (!within_table)
-      return false;
     gv->begin_tr ();
     pretty_printer *pp = gv->get_pp ();
 
-    gv->begin_td ();
-    pp_string (pp, "BEFORE");
-    pp_printf (pp, " (scc: %i)", m_eg.get_scc_id (n));
-    gv->end_td ();
-
-    unsigned i;
-    exploded_node *enode;
-    bool had_enode = false;
-    FOR_EACH_VEC_ELT (*m_enodes_per_snodes[n.m_index], i, enode)
+    if (m_enodes_per_snode_id[n.m_id].empty ())
+      pp_string (pp, "<TD BGCOLOR=\"red\">UNREACHED</TD>");
+    else
       {
-	gcc_assert (enode->get_supernode () == &n);
-	const program_point &point = enode->get_point ();
-	if (point.get_kind () != PK_BEFORE_SUPERNODE)
-	  continue;
-	print_enode (gv, enode);
-	had_enode = true;
+	/* Adding an empty TD here makes the actual enodes
+	   be right-aligned and tightly packed, greatly
+	   improving the readability of the graph.  */
+	pp_string (pp, "<TD></TD>");
+	for (auto enode : m_enodes_per_snode_id[n.m_id])
+	  {
+	    gcc_assert (enode->get_supernode () == &n);
+	    print_enode (gv, enode);
+	  }
       }
-    if (!had_enode)
-      pp_string (pp, "<TD BGCOLOR=\"red\">UNREACHED</TD>");
+
     pp_flush (pp);
     gv->end_tr ();
-    return true;
   }
 
-  /* Show exploded nodes for STMT.  */
-  void add_stmt_annotations (graphviz_out *gv, const gimple *stmt,
-			     bool within_row)
-    const final override
+  void
+  add_extra_objects (graphviz_out *gv) const final override
   {
-    if (!within_row)
-      return;
     pretty_printer *pp = gv->get_pp ();
 
-    const supernode *snode
-      = m_eg.get_supergraph ().get_supernode_for_stmt (stmt);
-    unsigned i;
-    exploded_node *enode;
-    bool had_td = false;
-    FOR_EACH_VEC_ELT (*m_enodes_per_snodes[snode->m_index], i, enode)
-      {
-	const program_point &point = enode->get_point ();
-	if (point.get_kind () != PK_BEFORE_STMT)
-	  continue;
-	if (point.get_stmt () != stmt)
-	  continue;
-	print_enode (gv, enode);
-	had_td = true;
-      }
+    pp_string (pp, "en_0 [shape=none,margin=0,style=filled,label=<<TABLE><TR>");
+    print_enode (gv, m_eg.m_nodes[0]);
+    pp_string (pp, "</TR></TABLE>>];\n\n");
     pp_flush (pp);
-    if (!had_td)
-      {
-	gv->begin_td ();
-	gv->end_td ();
-      }
-  }
-
-  /* Show exploded nodes for AFTER_SUPERNODE points after N.  */
-  bool add_after_node_annotations (graphviz_out *gv, const supernode &n)
-    const final override
-  {
-    gv->begin_tr ();
-    pretty_printer *pp = gv->get_pp ();
-
-    gv->begin_td ();
-    pp_string (pp, "AFTER");
-    gv->end_td ();
 
     unsigned i;
-    exploded_node *enode;
-    FOR_EACH_VEC_ELT (*m_enodes_per_snodes[n.m_index], i, enode)
+    exploded_edge *eedge;
+    FOR_EACH_VEC_ELT (m_eg.m_edges, i, eedge)
       {
-	gcc_assert (enode->get_supernode () == &n);
-	const program_point &point = enode->get_point ();
-	if (point.get_kind () != PK_AFTER_SUPERNODE)
-	  continue;
-	print_enode (gv, enode);
+	print_enode_port (pp, *eedge->m_src, "s");
+	pp_string (pp, " -> ");
+	print_enode_port (pp, *eedge->m_dest, "n");
+	dot::attr_list attrs;
+	attrs.add (dot::id ("style"), dot::id ("dotted"));
+	if (eedge->m_custom_info)
+	  {
+	    pretty_printer info_pp;
+	    pp_format_decoder (&info_pp) = default_tree_printer;
+	    eedge->m_custom_info->print (&info_pp);
+	    attrs.add (dot::id ("label"),
+		       dot::id (pp_formatted_text (&info_pp)));
+	  }
+	dot::writer w (*pp);
+	attrs.print (w);
+	pp_newline (pp);
       }
-    pp_flush (pp);
-    gv->end_tr ();
-    return true;
   }
 
 private:
+  void
+  print_enode_port (pretty_printer *pp,
+		    const exploded_node &enode,
+		    const char *compass_pt) const
+  {
+    if (const supernode *snode = enode.get_supernode ())
+      pp_printf (pp, "node_%i:en_%i:%s",
+		 snode->m_id, enode.m_index, compass_pt);
+    else
+      pp_printf (pp, "en_%i:%s",
+		 enode.m_index, compass_pt);
+  }
+
   /* Concisely print a TD element for ENODE, showing the index, status,
      and any saved_diagnostics at the enode.  Colorize it to show sm-state.
 
@@ -6367,7 +4992,7 @@ private:
     pretty_printer *pp = gv->get_pp ();
     pp_printf (pp, "<TD BGCOLOR=\"%s\">",
 	       enode->get_dot_fillcolor ());
-    pp_printf (pp, "<TABLE BORDER=\"0\">");
+    pp_printf (pp, "<TABLE BORDER=\"0\" PORT=\"en_%i\">", enode->m_index);
     gv->begin_trtd ();
     pp_printf (pp, "EN: %i", enode->m_index);
     switch (enode->get_status ())
@@ -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<auto_vec <exploded_node *> > m_enodes_per_snodes;
+  std::vector<std::vector <exploded_node *> > 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 bf772eef4ee2..9197541bb133 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 1d31097ec9c1..52e6bf09d766 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<pending_diagnostic> d,
-	     const stmt_finder *custom_finder = nullptr) final override;
+  bool
+  warn_at (std::unique_ptr<pending_diagnostic> d,
+	   pending_location &&ploc) final override;
   void add_note (std::unique_ptr<pending_note> pn) final override;
   void add_event (std::unique_ptr<checker_event> event) final override;
   void on_svalue_leak (const svalue *) override;
@@ -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<eg_traits>
        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<eg_traits>
 
   std::unique_ptr<json::object> to_json (const extrinsic_state &ext_state) const;
 
-  /* The result of on_stmt.  */
-  struct on_stmt_flags
-  {
-    on_stmt_flags () : m_terminate_path (false)
-    {}
-
-    static on_stmt_flags terminate_path ()
-    {
-      return on_stmt_flags (true);
-    }
-
-    /* Should we stop analyzing this path (on_stmt may have already
-       added nodes/edges, e.g. when handling longjmp).  */
-    bool m_terminate_path : 1;
-
-  private:
-    on_stmt_flags (bool terminate_path)
-    : m_terminate_path (terminate_path)
-    {}
-  };
-
-  on_stmt_flags on_stmt (exploded_graph &eg,
-			 const supernode *snode,
-			 const gimple *stmt,
-			 program_state *state,
-			 uncertainty_t *uncertainty,
-			 bool *out_could_have_done_work,
-			 path_context *path_ctxt);
-  void on_stmt_pre (exploded_graph &eg,
-		    const gimple *stmt,
-		    program_state *state,
-		    bool *out_terminate_path,
-		    bool *out_unknown_side_effects,
-		    region_model_context *ctxt);
-  void on_stmt_post (const gimple *stmt,
-		     program_state *state,
-		     bool unknown_side_effects,
-		     region_model_context *ctxt);
-
-  on_stmt_flags replay_call_summaries (exploded_graph &eg,
-				       const supernode *snode,
-				       const gcall &call_stmt,
-				       program_state *state,
-				       path_context *path_ctxt,
-				       const function &called_fn,
-				       per_function_data &called_fn_data,
-				       region_model_context *ctxt);
-  void replay_call_summary (exploded_graph &eg,
-			    const supernode *snode,
-			    const gcall &call_stmt,
-			    program_state *state,
-			    path_context *path_ctxt,
-			    const function &called_fn,
-			    call_summary &summary,
-			    region_model_context *ctxt);
-
-  bool on_edge (exploded_graph &eg,
-		const superedge *succ,
-		program_point *next_point,
-		program_state *next_state,
-		uncertainty_t *uncertainty);
   void on_longjmp (exploded_graph &eg,
 		   const gcall &call,
 		   program_state *new_state,
 		   region_model_context *ctxt);
   void on_throw (exploded_graph &eg,
 		 const gcall &call,
+		 const program_point &after_throw_point,
 		 program_state *new_state,
 		 bool is_rethrow,
 		 region_model_context *ctxt);
-  void on_resx (exploded_graph &eg,
-		const gresx &resx,
-		program_state *new_state,
-		region_model_context *ctxt);
 
   void detect_leaks (exploded_graph &eg);
 
@@ -329,6 +266,10 @@ class exploded_node : public dnode<eg_traits>
   {
     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<eg_traits>
   {
     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<eg_traits>
 
   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<call_summary *> 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<unsigned> 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<json::object> 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<json::object> to_json () const;
@@ -954,8 +921,6 @@ private:
 
   call_string_data_map_t m_per_call_string_data;
 
-  auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
-
   /* Functions with a top-level enode, to make add_function_entry
      be idempotent, for use in handling callbacks.  */
   hash_set<function *> m_functions_with_enodes;
@@ -996,17 +961,15 @@ class feasibility_problem
 public:
   feasibility_problem (unsigned eedge_idx,
 		       const exploded_edge &eedge,
-		       const gimple *last_stmt,
 		       std::unique_ptr<rejected_constraint> rc)
   : m_eedge_idx (eedge_idx), m_eedge (eedge),
-    m_last_stmt (last_stmt), m_rc (std::move (rc))
+    m_rc (std::move (rc))
   {}
 
   void dump_to_pp (pretty_printer *pp) const;
 
   unsigned m_eedge_idx;
   const exploded_edge &m_eedge;
-  const gimple *m_last_stmt;
   std::unique_ptr<rejected_constraint> m_rc;
 };
 
@@ -1028,9 +991,10 @@ public:
 			      const exploded_edge *eedge,
 			      region_model_context *ctxt,
 			      std::unique_ptr<rejected_constraint> *out_rc);
-  void update_for_stmt (const gimple *stmt);
 
+  region_model &get_model () { return m_model; }
   const region_model &get_model () const { return m_model; }
+  auto_sbitmap &get_snodes_visited () { return m_snodes_visited; }
   const auto_sbitmap &get_snodes_visited () const { return m_snodes_visited; }
 
   void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
@@ -1044,20 +1008,6 @@ private:
 
 typedef shortest_paths<eg_traits, exploded_path> shortest_exploded_paths;
 
-/* Abstract base class for use when passing nullptr as the stmt for
-   a possible warning, allowing the choice of stmt to be deferred
-   until after we have an emission path (and know we're emitting a
-   warning).  */
-
-class stmt_finder
-{
-public:
-  virtual ~stmt_finder () {}
-  virtual std::unique_ptr<stmt_finder> clone () const = 0;
-  virtual const gimple *find_stmt (const exploded_path &epath) = 0;
-  virtual void update_event_loc_info (event_loc_info &) = 0;
-};
-
 // TODO: split the above up?
 
 } // namespace ana
diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc
index 25a97e7c12cf..8d424f95d56e 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 000000000000..1077f37ea88a
--- /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 <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_IMPL_SM_CONTEXT
+#define GCC_ANALYZER_IMPL_SM_CONTEXT
+
+namespace ana {
+
+/* Concrete implementation of sm_context, wiring it up to the rest of this
+   file.  */
+
+class impl_sm_context : public sm_context
+{
+public:
+  impl_sm_context (exploded_graph &eg,
+		   int sm_idx,
+		   const state_machine &sm,
+		   exploded_node *enode_for_diag,
+		   const program_state *old_state,
+		   program_state *new_state,
+		   const sm_state_map *old_smap,
+		   sm_state_map *new_smap,
+		   path_context *path_ctxt,
+		   bool unknown_side_effects = false)
+  : sm_context (sm_idx, sm),
+    m_logger (eg.get_logger ()),
+    m_eg (eg), m_enode_for_diag (enode_for_diag),
+    m_old_state (old_state), m_new_state (new_state),
+    m_old_smap (old_smap), m_new_smap (new_smap),
+    m_path_ctxt (path_ctxt),
+    m_unknown_side_effects (unknown_side_effects)
+  {
+  }
+
+  logger *get_logger () const { return m_logger.get_logger (); }
+
+  tree get_fndecl_for_call (const gcall &call) final override
+  {
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
+       nullptr, &call);
+    region_model *model = m_new_state->m_region_model;
+    return model->get_fndecl_for_call (call, &old_ctxt);
+  }
+
+  state_machine::state_t get_state (tree var) final override
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    /* Use nullptr ctxt on this get_rvalue call to avoid triggering
+       uninitialized value warnings.  */
+    const svalue *var_old_sval
+      = m_old_state->m_region_model->get_rvalue (var, nullptr);
+
+    state_machine::state_t current
+      = m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
+    return current;
+  }
+  state_machine::state_t get_state (const svalue *sval) final override
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    state_machine::state_t current
+      = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+    return current;
+  }
+
+
+  void set_next_state (tree var,
+		       state_machine::state_t to,
+		       tree origin) final override
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    const svalue *var_new_sval
+      = m_new_state->m_region_model->get_rvalue (var, nullptr);
+    const svalue *origin_new_sval
+      = m_new_state->m_region_model->get_rvalue (origin, nullptr);
+
+    /* We use the new sval here to avoid issues with uninitialized values.  */
+    state_machine::state_t current
+      = m_old_smap->get_state (var_new_sval, m_eg.get_ext_state ());
+    if (logger)
+      logger->log ("%s: state transition of %qE: %s -> %s",
+		   m_sm.get_name (),
+		   var,
+		   current->get_name (),
+		   to->get_name ());
+    m_new_smap->set_state (m_new_state->m_region_model, var_new_sval,
+			   to, origin_new_sval, m_eg.get_ext_state ());
+  }
+
+  void set_next_state (const svalue *sval,
+		       state_machine::state_t to,
+		       tree origin) final override
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, nullptr, nullptr, nullptr/*m_enode->get_state ()*/,
+       nullptr, nullptr);
+
+    const svalue *origin_new_sval
+      = m_new_state->m_region_model->get_rvalue (origin, nullptr);
+
+    state_machine::state_t current
+      = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+    if (logger)
+      {
+	logger->start_log_line ();
+	logger->log_partial ("%s: state transition of ",
+			     m_sm.get_name ());
+	sval->dump_to_pp (logger->get_printer (), true);
+	logger->log_partial (": %s -> %s",
+			     current->get_name (),
+			     to->get_name ());
+	logger->end_log_line ();
+      }
+    m_new_smap->set_state (m_new_state->m_region_model, sval,
+			   to, origin_new_sval, m_eg.get_ext_state ());
+  }
+
+  void warn (tree var,
+	     std::unique_ptr<pending_diagnostic> d) final override
+  {
+    LOG_FUNC (get_logger ());
+    gcc_assert (d);
+    const svalue *var_old_sval
+      = m_old_state->m_region_model->get_rvalue (var, nullptr);
+    state_machine::state_t current
+      = (var
+	 ? m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ())
+	 : m_old_smap->get_global_state ());
+    bool terminate_path = d->terminate_path_p ();
+    pending_location ploc (m_enode_for_diag);
+    m_eg.get_diagnostic_manager ().add_diagnostic
+      (&m_sm, std::move (ploc),
+       var, var_old_sval, current, std::move (d));
+    if (m_path_ctxt
+	&& terminate_path
+	&& flag_analyzer_suppress_followups)
+      m_path_ctxt->terminate_path ();
+  }
+
+  void warn (const svalue *sval,
+	     std::unique_ptr<pending_diagnostic> d) final override
+  {
+    LOG_FUNC (get_logger ());
+    gcc_assert (d);
+    state_machine::state_t current
+      = (sval
+	 ? m_old_smap->get_state (sval, m_eg.get_ext_state ())
+	 : m_old_smap->get_global_state ());
+    bool terminate_path = d->terminate_path_p ();
+    pending_location ploc (m_enode_for_diag);
+    m_eg.get_diagnostic_manager ().add_diagnostic
+      (&m_sm, std::move (ploc),
+       NULL_TREE, sval, current, std::move (d));
+    if (m_path_ctxt
+	&& terminate_path
+	&& flag_analyzer_suppress_followups)
+      m_path_ctxt->terminate_path ();
+  }
+
+  /* Hook for picking more readable trees for SSA names of temporaries,
+     so that rather than e.g.
+       "double-free of '<unknown>'"
+     we can print:
+       "double-free of 'inbuf.data'".  */
+
+  tree get_diagnostic_tree (tree expr) final override
+  {
+    /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
+       likely to be the least surprising tree to report.  */
+    if (TREE_CODE (expr) != SSA_NAME)
+      return expr;
+    if (SSA_NAME_VAR (expr) != NULL)
+      return expr;
+
+    gcc_assert (m_new_state);
+    const svalue *sval = m_new_state->m_region_model->get_rvalue (expr, nullptr);
+    /* Find trees for all regions storing the value.  */
+    if (tree t = m_new_state->m_region_model->get_representative_tree (sval))
+      return t;
+    else
+      return expr;
+  }
+
+  tree get_diagnostic_tree (const svalue *sval) final override
+  {
+    return m_new_state->m_region_model->get_representative_tree (sval);
+  }
+
+  state_machine::state_t get_global_state () const final override
+  {
+    return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
+  }
+
+  void set_global_state (state_machine::state_t state) final override
+  {
+    m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
+  }
+
+  void clear_all_per_svalue_state () final override
+  {
+    m_new_state->m_checker_states[m_sm_idx]->clear_all_per_svalue_state ();
+  }
+
+  void on_custom_transition (custom_transition *transition) final override
+  {
+    transition->impl_transition (&m_eg,
+				 const_cast<exploded_node *> (m_enode_for_diag),
+				 m_sm_idx);
+  }
+
+  tree is_zero_assignment (const gimple *stmt) final override
+  {
+    const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+    if (!assign_stmt)
+     return NULL_TREE;
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, nullptr, nullptr, stmt);
+    if (const svalue *sval
+	= m_new_state->m_region_model->get_gassign_result (assign_stmt,
+							    &old_ctxt))
+      if (tree cst = sval->maybe_get_constant ())
+	if (::zerop(cst))
+	  return gimple_assign_lhs (assign_stmt);
+    return NULL_TREE;
+  }
+
+  path_context *get_path_context () const final override
+  {
+    return m_path_ctxt;
+  }
+
+  bool unknown_side_effects_p () const final override
+  {
+    return m_unknown_side_effects;
+  }
+
+  const program_state *get_old_program_state () const final override
+  {
+    return m_old_state;
+  }
+
+  const program_state *get_new_program_state () const final override
+  {
+    return m_new_state;
+  }
+
+  location_t get_emission_location () const final override
+  {
+    return pending_location (m_enode_for_diag).get_location ();
+  }
+
+  log_user m_logger;
+  exploded_graph &m_eg;
+  exploded_node *m_enode_for_diag;
+  const program_state *m_old_state;
+  program_state *m_new_state;
+  const sm_state_map *m_old_smap;
+  sm_state_map *m_new_smap;
+  path_context *m_path_ctxt;
+
+  /* Are we handling an external function with unknown side effects?  */
+  bool m_unknown_side_effects;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_IMPL_SM_CONTEXT */
diff --git a/gcc/analyzer/infinite-loop.cc b/gcc/analyzer/infinite-loop.cc
index a53807c2ddb6..05c23e0ef4d0 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<perpetual_start_cfg_edge_event>
+	if (auto base_op = eedge->m_sedge->get_op ())
+	  if (auto control_flow_op = base_op->dyn_cast_control_flow_op ())
+	    {
+	      if (control_flow_op->get_kind () == operation::switch_edge)
+		{
+		  const switch_case_op &case_op
+		    = *(const switch_case_op *)base_op;
+		  if (case_op.implicitly_created_default_p ())
+		    {
+		      emission_path->add_event
+			(std::make_unique<perpetual_start_cfg_edge_event>
+			 (*eedge,
+			  loc_info_from,
+			  control_flow_op));
+		      emission_path->add_event
+			(std::make_unique<end_cfg_edge_event>
+			 (*eedge,
+			  loc_info_to,
+			  control_flow_op));
+		    }
+		}
+	      else if (cfg_edge->flags & EDGE_TRUE_VALUE)
+		{
+		  emission_path->add_event
+		    (std::make_unique<perpetual_start_cfg_edge_event>
 		     (*eedge,
-		      loc_info_from));
-		emission_path->add_event
-		  (std::make_unique<end_cfg_edge_event>
+		      loc_info_from,
+		      control_flow_op));
+		  emission_path->add_event
+		    (std::make_unique<end_cfg_edge_event>
 		     (*eedge,
-		      loc_info_to));
-	      }
-	  }
+		      loc_info_to,
+		      control_flow_op));
+		}
+	      else if (cfg_edge->flags & EDGE_FALSE_VALUE)
+		{
+		  emission_path->add_event
+		    (std::make_unique<perpetual_start_cfg_edge_event>
+		     (*eedge,
+		      loc_info_from,
+		      control_flow_op));
+		  emission_path->add_event
+		    (std::make_unique<end_cfg_edge_event>
+		     (*eedge,
+		      loc_info_to,
+		      control_flow_op));
+		}
+	    }
 
-	if (cfg_sedge->true_value_p ())
-	  {
-	    emission_path->add_event
-	      (std::make_unique<perpetual_start_cfg_edge_event>
-		 (*eedge,
-		  loc_info_from));
-	    emission_path->add_event
-	      (std::make_unique<end_cfg_edge_event>
-		 (*eedge,
-		  loc_info_to));
-	  }
-	else if (cfg_sedge->false_value_p ())
+	if (cfg_edge->flags & EDGE_DFS_BACK)
 	  {
 	    emission_path->add_event
-	      (std::make_unique<perpetual_start_cfg_edge_event>
-		 (*eedge,
-		  loc_info_from));
+	      (std::make_unique<looping_back_event> (*eedge, loc_info_from,
+						     nullptr));
 	    emission_path->add_event
 	      (std::make_unique<end_cfg_edge_event>
 		 (*eedge,
-		  loc_info_to));
-	  }
-	else if (cfg_sedge->back_edge_p ())
-	  {
-	    emission_path->add_event
-	      (std::make_unique<looping_back_event> (*eedge, loc_info_from));
-	    emission_path->add_event
-	      (std::make_unique<end_cfg_edge_event>
-		 (*eedge,
-		  loc_info_to));
+		  loc_info_to,
+		  nullptr));
 	  }
       }
   }
@@ -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<infinite_loop> inf_loop
 	    = starts_infinite_loop_p (*enode, *this, get_logger ()))
 	{
-	  const supernode *snode = enode->get_supernode ();
-
 	  if (get_logger ())
 	    get_logger ()->log ("EN: %i from starts_infinite_loop_p",
 				enode->m_index);
@@ -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<infinite_loop_diagnostic> (std::move (inf_loop));
-	  get_diagnostic_manager ().add_diagnostic (ploc, std::move (d));
+	  get_diagnostic_manager ().add_diagnostic (std::move (ploc),
+						    std::move (d));
 	}
     }
 }
diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc
index cde3016b4449..1578cd6a0af1 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<warning_event>
        (event_loc_info (m_new_entry_enode->get_supernode
-			  ()->get_start_location (),
+			  ()->get_location (),
 			m_callee_fndecl,
 			m_new_entry_enode->get_stack_depth ()),
 	enode,
@@ -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<const feasible_node *> (fedge->m_dest);
     const region_model &model = dst_fnode->get_state ().get_model ();
 
-    if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    if (const gcond *cond_stmt = dyn_cast <const gcond *> (&last_stmt))
       {
 	if (expr_uses_conjured_svalue_p (model, gimple_cond_lhs (cond_stmt)))
 	  return true;
@@ -264,7 +265,7 @@ private:
 	  return true;
       }
     else if (const gswitch *switch_stmt
-	       = dyn_cast <const gswitch *> (last_stmt))
+	       = dyn_cast <const gswitch *> (&last_stmt))
       {
 	if (expr_uses_conjured_svalue_p (model,
 					 gimple_switch_index (switch_stmt)))
@@ -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<infinite_recursion_diagnostic> (prev_entry_enode,
 						      enode,
 						      fndecl));
diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
index 01a98b0163e5..ffd1e53b6677 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 000000000000..f7dd3a6d698e
--- /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 <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "analyzer/common.h"
+
+#include "gimple-pretty-print.h"
+#include "gimple-iterator.h"
+#include "tree-cfg.h"
+#include "tree-dfa.h"
+#include "fold-const.h"
+#include "cgraph.h"
+#include "text-art/dump.h"
+#include "text-art/tree-widget.h"
+
+#include "analyzer/ops.h"
+#include "analyzer/call-details.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/impl-sm-context.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/call-summary.h"
+#include "analyzer/call-info.h"
+#include "analyzer/analysis-plan.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+event_loc_info::event_loc_info (const exploded_node *enode)
+{
+  if (enode)
+    {
+      m_loc = enode->get_location ();
+      m_fndecl = enode->get_point ().get_fndecl ();
+      m_depth = enode->get_stack_depth ();
+    }
+  else
+    {
+      m_loc = UNKNOWN_LOCATION;
+      m_fndecl = NULL_TREE;
+      m_depth = 0;
+    }
+}
+
+event_loc_info::event_loc_info (const program_point &point)
+{
+  m_loc = point.get_location ();
+  m_fndecl = point.get_fndecl ();
+  m_depth = point.get_stack_depth ();
+}
+
+// struct operation_context
+
+void
+operation_context::dump () const
+{
+  fprintf (stderr, "src enode: EN: %i\n", m_src_enode.m_index);
+  m_src_enode.dump (m_eg.get_ext_state ());
+
+  fprintf (stderr, "superedge\n");
+  pretty_printer pp;
+  pp.set_output_stream (stderr);
+  m_sedge.dump (&pp);
+}
+
+logger *
+operation_context::get_logger () const
+{
+  return m_eg.get_logger ();
+}
+
+const extrinsic_state &
+operation_context::get_ext_state () const
+{
+  return m_eg.get_ext_state ();
+}
+
+const program_point &
+operation_context::get_initial_point () const
+{
+  return m_src_enode.get_point ();
+}
+
+const program_state &
+operation_context::get_initial_state () const
+{
+  return m_src_enode.get_state ();
+}
+
+const supergraph &
+operation_context::get_supergraph () const
+{
+  return m_eg.get_supergraph ();
+}
+
+program_point
+operation_context::get_next_intraprocedural_point () const
+{
+  /* All edges are intraprocedural.  */
+  gcc_assert (m_sedge.m_src->get_function ()
+	      == m_sedge.m_dest->get_function ());
+  return program_point (m_sedge.m_dest,
+			m_src_enode.get_point ().get_call_string ());
+}
+
+void
+operation_context::add_outcome (const program_point &dst_point,
+				program_state dst_state,
+				bool could_do_work,
+				uncertainty_t *uncertainty,
+				std::unique_ptr<custom_edge_info> info)
+{
+  const program_state &src_state = get_initial_state ();
+  impl_region_model_context ctxt (m_eg, &m_src_enode,
+				  &src_state, &dst_state,
+				  uncertainty, nullptr);
+  program_state::detect_leaks (src_state, dst_state, nullptr,
+			       get_ext_state (), &ctxt);
+
+  if (exploded_node *dst_enode
+      = m_eg.get_or_create_node (dst_point, dst_state, &m_src_enode))
+    {
+      m_eg.add_edge (&m_src_enode, dst_enode, &m_sedge, could_do_work,
+		     std::move (info));
+      m_eg.detect_infinite_recursion (dst_enode);
+    }
+}
+
+class op_region_model_context : public impl_region_model_context
+{
+public:
+  op_region_model_context (operation_context &op_ctxt,
+			   program_state &dst_state)
+  : impl_region_model_context (op_ctxt.m_eg,
+			       &op_ctxt.m_src_enode,
+			       &op_ctxt.get_initial_state (),
+			       &dst_state,
+			       nullptr,
+			       &m_path_context)
+  {
+  }
+
+  bool terminate_path_p () const
+  {
+    return m_path_context.terminate_path_p ();
+  }
+
+private:
+  class op_path_context : public path_context
+  {
+  public:
+    op_path_context ()
+    : m_terminate_path (false)
+    {
+    }
+
+    void bifurcate (std::unique_ptr<custom_edge_info>) final override
+    {
+      gcc_unreachable ();
+    }
+
+    void terminate_path () final override
+    {
+      m_terminate_path = true;
+    }
+
+    bool terminate_path_p () const final override
+    {
+      return m_terminate_path;
+    }
+  private:
+    bool m_terminate_path;
+  } m_path_context;
+};
+
+// class gimple_stmt_op : public operation
+
+void
+gimple_stmt_op::print_as_edge_label (pretty_printer *pp,
+				     bool /*user_facing*/) const
+{
+  pp_gimple_stmt_1 (pp, &m_stmt, 0, (dump_flags_t)0);
+}
+
+bool
+gimple_stmt_op::defines_ssa_name_p (const_tree ssa_name) const
+{
+  return &m_stmt == SSA_NAME_DEF_STMT (ssa_name);
+}
+
+bool
+gimple_stmt_op::supports_bulk_merge_p () const
+{
+  return false;
+}
+
+/* Subclass of path_context for use within operation::execute implementations
+   so that we can split states e.g. at "realloc" calls.  */
+
+class impl_path_context : public path_context
+{
+public:
+  impl_path_context (const program_state *cur_state,
+		     logger *logger)
+  : m_cur_state (cur_state),
+    m_logger (logger),
+    m_terminate_path (false)
+  {
+  }
+
+  bool bifurcation_p () const
+  {
+    return m_custom_eedge_infos.length () > 0;
+  }
+
+  const program_state &get_state_at_bifurcation () const
+  {
+    gcc_assert (m_state_at_bifurcation);
+    return *m_state_at_bifurcation;
+  }
+
+  void
+  bifurcate (std::unique_ptr<custom_edge_info> info) final override
+  {
+    if (m_logger)
+      m_logger->log ("bifurcating path");
+
+    if (m_state_at_bifurcation)
+      /* Verify that the state at bifurcation is consistent when we
+	 split into multiple out-edges.  */
+      gcc_assert (*m_state_at_bifurcation == *m_cur_state);
+    else
+      /* Take a copy of the cur_state at the moment when bifurcation
+	 happens.  */
+      m_state_at_bifurcation
+	= std::unique_ptr<program_state> (new program_state (*m_cur_state));
+
+    /* Take ownership of INFO.  */
+    m_custom_eedge_infos.safe_push (info.release ());
+  }
+
+  void terminate_path () final override
+  {
+    if (m_logger)
+      m_logger->log ("terminating path");
+    m_terminate_path = true;
+  }
+
+  bool terminate_path_p () const final override
+  {
+    return m_terminate_path;
+  }
+
+  const vec<custom_edge_info *> & get_custom_eedge_infos ()
+  {
+    return m_custom_eedge_infos;
+  }
+
+private:
+  const program_state *m_cur_state;
+
+  logger *m_logger;
+
+  /* Lazily-created copy of the state before the split.  */
+  std::unique_ptr<program_state> m_state_at_bifurcation;
+
+  auto_vec <custom_edge_info *> m_custom_eedge_infos;
+
+  bool m_terminate_path;
+};
+
+DEBUG_FUNCTION void
+operation::dump () const
+{
+  tree_dump_pretty_printer pp (stderr);
+  print_as_edge_label (&pp, false);
+  pp_newline (&pp);
+}
+
+void
+operation::handle_on_stmt_for_state_machines (operation_context &op_ctxt,
+					      program_state &dst_state,
+					      path_context *path_ctxt,
+					      bool &unknown_side_effects,
+					      const gimple &stmt)
+{
+  const program_state &old_state = op_ctxt.get_initial_state ();
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx);
+      const sm_state_map *old_smap
+	= old_state.m_checker_states[sm_idx];
+      sm_state_map *new_smap = dst_state.m_checker_states[sm_idx];
+      impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm,
+			       &op_ctxt.m_src_enode,
+			       &old_state,
+			       &dst_state,
+			       old_smap, new_smap, path_ctxt,
+			       unknown_side_effects);
+
+      /* Allow the state_machine to handle the stmt.  */
+      if (sm.on_stmt (sm_ctxt, &stmt))
+	unknown_side_effects = false;
+    }
+}
+
+void
+gimple_stmt_op::
+walk_load_store_addr_ops (void *data,
+			  walk_stmt_load_store_addr_fn load_cb,
+			  walk_stmt_load_store_addr_fn store_cb,
+			  walk_stmt_load_store_addr_fn addr_cb) const
+{
+  walk_stmt_load_store_addr_ops (const_cast<gimple *>(&m_stmt), data,
+				 load_cb, store_cb, addr_cb);
+}
+
+void
+gimple_stmt_op::execute (operation_context &op_ctxt) const
+{
+  auto logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+  if (logger)
+    {
+      logger->start_log_line ();
+      pp_gimple_stmt_1 (logger->get_printer (), &get_stmt (), 0,
+			(dump_flags_t)0);
+      logger->end_log_line ();
+    }
+  execute_on_state (op_ctxt,
+		    /* Pass in a copy.  */
+		    op_ctxt.get_initial_state ());
+}
+
+void
+gimple_stmt_op::execute_on_state (operation_context &op_ctxt,
+				  program_state dst_state) const
+{
+  auto logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+
+  auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+  const program_state &old_state  = op_ctxt.get_initial_state ();
+
+  bool unknown_side_effects = false;
+  bool could_have_done_work = false;
+
+  impl_path_context path_ctxt (&dst_state, logger);
+  uncertainty_t uncertainty;
+  impl_region_model_context ctxt (op_ctxt.m_eg,
+				  &op_ctxt.m_src_enode,
+				  &old_state,
+				  &dst_state,
+				  &uncertainty,
+				  &path_ctxt,
+				  &m_stmt,
+				  &could_have_done_work);
+
+  dst_state.m_region_model->on_stmt_pre (&get_stmt (),
+					 &unknown_side_effects,
+					 &ctxt);
+
+  handle_on_stmt_for_state_machines (op_ctxt,
+				     dst_state,
+				     &path_ctxt,
+				     unknown_side_effects,
+				     m_stmt);
+
+  if (path_ctxt.terminate_path_p ())
+    return;
+
+  if (const gcall *call = dyn_cast <const gcall *> (&m_stmt))
+    dst_state.m_region_model->on_call_post (*call, unknown_side_effects, &ctxt);
+
+  if (!path_ctxt.terminate_path_p ())
+    op_ctxt.add_outcome (dst_point, dst_state, could_have_done_work,
+			 &uncertainty);
+
+  /* If we have custom edge infos, "bifurcate" the state
+     accordingly, potentially creating a new state/enode/eedge
+     instances.  For example, to handle a "realloc" call, we
+     might split into 3 states, for the "failure",
+     "resizing in place", and "moving to a new buffer" cases.  */
+  for (auto edge_info_iter : path_ctxt.get_custom_eedge_infos ())
+    {
+      /* Take ownership of the edge infos from the path_ctxt.  */
+      std::unique_ptr<custom_edge_info> edge_info (edge_info_iter);
+      if (logger)
+	{
+	  logger->start_log_line ();
+	  logger->log_partial ("bifurcating for edge: ");
+	  edge_info->print (logger->get_printer ());
+	  logger->end_log_line ();
+	}
+      program_state bifurcated_new_state
+	(path_ctxt.get_state_at_bifurcation ());
+
+      /* Apply edge_info to state.  */
+      impl_region_model_context
+	bifurcation_ctxt (op_ctxt.m_eg,
+			  &op_ctxt.m_src_enode,
+			  &path_ctxt.get_state_at_bifurcation (),
+			  &bifurcated_new_state,
+			  nullptr, // uncertainty_t *uncertainty
+			  nullptr, // path_context *path_ctxt
+			  &m_stmt);
+      if (edge_info->update_state (&bifurcated_new_state,
+				   nullptr, /* no exploded_edge yet.  */
+				   &bifurcation_ctxt))
+	{
+	  if (exploded_node *next2
+	      = edge_info->create_enode
+	      (op_ctxt.m_eg,
+	       dst_point,
+	       std::move (bifurcated_new_state),
+	       &op_ctxt.m_src_enode,
+	       &bifurcation_ctxt))
+	    {
+	      op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode, next2, nullptr,
+				     true /* assume that work could be done */,
+				     std::move (edge_info));
+	    }
+	}
+    }
+}
+
+bool
+gimple_stmt_op::
+execute_for_feasibility (const exploded_edge &,
+			 feasibility_state &fstate,
+			 region_model_context *ctxt,
+			 std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+  region_model &model = fstate.get_model ();
+  bool unknown_side_effects;
+  model.on_stmt_pre (&m_stmt, &unknown_side_effects, ctxt);
+
+  if (const gcall *call = dyn_cast <const gcall *> (&m_stmt))
+    model.on_call_post (*call, unknown_side_effects, ctxt);
+
+  return true;
+}
+
+/* An sm_context for adding state_change_event on assignments to NULL,
+   where the default state isn't m_start.  Storing such state in the
+   sm_state_map would lead to bloat of the exploded_graph, so we want
+   to leave it as a default state, and inject state change events here
+   when we have a diagnostic.
+   Find transitions of constants, for handling on_zero_assignment.  */
+
+struct null_assignment_sm_context : public sm_context
+{
+  null_assignment_sm_context (int sm_idx,
+			      const state_machine &sm,
+			      const program_state *old_state,
+			      const program_state *new_state,
+			      const gimple *stmt,
+			      const program_point *point,
+			      checker_path *emission_path,
+			      const extrinsic_state &ext_state)
+  : sm_context (sm_idx, sm), m_old_state (old_state), m_new_state (new_state),
+    m_stmt (stmt), m_point (point), m_emission_path (emission_path),
+    m_ext_state (ext_state)
+  {
+  }
+
+  tree get_fndecl_for_call (const gcall &/*call*/) final override
+  {
+    return NULL_TREE;
+  }
+
+  state_machine::state_t get_state (tree var) final override
+  {
+    const svalue *var_old_sval
+      = m_old_state->m_region_model->get_rvalue (var, nullptr);
+    const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
+
+    state_machine::state_t current
+      = old_smap->get_state (var_old_sval, m_ext_state);
+
+    return current;
+  }
+
+  state_machine::state_t get_state (const svalue *sval) final override
+  {
+    const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
+    state_machine::state_t current = old_smap->get_state (sval, m_ext_state);
+    return current;
+  }
+
+  void set_next_state (tree var,
+		       state_machine::state_t to,
+		       tree origin ATTRIBUTE_UNUSED) final override
+  {
+    state_machine::state_t from = get_state (var);
+    if (from != m_sm.get_start_state ())
+      return;
+    if (!is_transition_to_null (to))
+      return;
+
+    const svalue *var_new_sval
+      = m_new_state->m_region_model->get_rvalue (var, nullptr);
+
+    m_emission_path->add_event
+      (std::make_unique<state_change_event> (event_loc_info (*m_point),
+					     m_stmt,
+					     m_sm,
+					     var_new_sval,
+					     from, to,
+					     nullptr,
+					     *m_new_state,
+					     nullptr));
+  }
+
+  void set_next_state (const svalue *sval,
+		       state_machine::state_t to,
+		       tree origin ATTRIBUTE_UNUSED) final override
+  {
+    state_machine::state_t from = get_state (sval);
+    if (from != m_sm.get_start_state ())
+      return;
+    if (!is_transition_to_null (to))
+      return;
+
+    m_emission_path->add_event
+      (std::make_unique<state_change_event> (event_loc_info (*m_point),
+					     m_stmt,
+					     m_sm,
+					     sval,
+					     from, to,
+					     nullptr,
+					     *m_new_state,
+					     nullptr));
+  }
+
+  void warn (tree, std::unique_ptr<pending_diagnostic>) final override
+  {
+  }
+  void warn (const svalue *, std::unique_ptr<pending_diagnostic>) final override
+  {
+  }
+
+  tree get_diagnostic_tree (tree expr) final override
+  {
+    return expr;
+  }
+
+  tree get_diagnostic_tree (const svalue *sval) final override
+  {
+    return m_new_state->m_region_model->get_representative_tree (sval);
+  }
+
+  state_machine::state_t get_global_state () const final override
+  {
+    return 0;
+  }
+
+  void set_global_state (state_machine::state_t) final override
+  {
+    /* No-op.  */
+  }
+
+  void clear_all_per_svalue_state () final override
+  {
+    /* No-op.  */
+  }
+
+  void on_custom_transition (custom_transition *) final override
+  {
+  }
+
+  tree is_zero_assignment (const gimple *stmt) final override
+  {
+    const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+    if (!assign_stmt)
+     return NULL_TREE;
+    if (const svalue *sval
+	= m_new_state->m_region_model->get_gassign_result (assign_stmt, nullptr))
+      if (tree cst = sval->maybe_get_constant ())
+	if (::zerop(cst))
+	  return gimple_assign_lhs (assign_stmt);
+    return NULL_TREE;
+  }
+
+  const program_state *get_old_program_state () const final override
+  {
+    return m_old_state;
+  }
+  const program_state *get_new_program_state () const final override
+  {
+    return m_new_state;
+  }
+
+  location_t get_emission_location () const final override
+  {
+    return UNKNOWN_LOCATION;
+  }
+
+  /* We only care about transitions to the "null" state
+     within sm-malloc.  Special-case this.  */
+  static bool is_transition_to_null (state_machine::state_t s)
+  {
+    return !strcmp (s->get_name (), "null");
+  }
+
+  const program_state *m_old_state;
+  const program_state *m_new_state;
+  const gimple *m_stmt;
+  const program_point *m_point;
+  checker_path *m_emission_path;
+  const extrinsic_state &m_ext_state;
+};
+
+void
+gimple_stmt_op::add_any_events_for_eedge (const exploded_edge &eedge,
+					  checker_path &out_path) const
+{
+  out_path.add_event
+    (std::make_unique<statement_event> (&get_stmt (),
+					eedge.m_dest->get_function ()->decl,
+					eedge.m_dest->get_stack_depth (),
+					eedge.m_dest->get_state ()));
+
+  /* Create state change events for assignment to NULL.
+     Iterate through the stmts in dst_enode, adding state change
+     events for them.  */
+  if (const gassign *assign = dyn_cast<const gassign *> (&m_stmt))
+    {
+      const program_point &src_point = eedge.m_src->get_point ();
+      const extrinsic_state &ext_state = out_path.get_ext_state ();
+      for (unsigned i = 0; i < ext_state.get_num_checkers (); i++)
+	{
+	  const state_machine &sm = ext_state.get_sm (i);
+	  null_assignment_sm_context sm_ctxt (i, sm,
+					      &eedge.m_src->get_state (),
+					      &eedge.m_dest->get_state (),
+					      assign,
+					      &src_point,
+					      &out_path,
+					      ext_state);
+	  sm.on_stmt (sm_ctxt, assign);
+	  // TODO: what about phi nodes?
+	}
+    }
+}
+
+// class gassign_op : public gimple_stmt_op
+
+// class greturn_op : public gimple_stmt_op
+
+void
+greturn_op::execute (operation_context &op_ctxt) const
+{
+  auto logger = op_ctxt.get_logger ();
+
+  auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+  const program_state &old_state  = op_ctxt.get_initial_state ();
+  program_state dst_state (old_state);
+
+  impl_path_context path_ctxt (&dst_state, logger);
+  uncertainty_t uncertainty;
+  impl_region_model_context ctxt (op_ctxt.m_eg,
+				  &op_ctxt.m_src_enode,
+
+				  /* TODO: should we be getting the ECs from the
+				     old state, rather than the new?  */
+				  &op_ctxt.get_initial_state (),
+				  &dst_state,
+				  &uncertainty,
+				  &path_ctxt,
+				  nullptr,
+				  nullptr);
+
+  tree callee = op_ctxt.get_initial_point ().get_function ()->decl;
+  tree lhs = DECL_RESULT (callee);
+
+  if (lhs && get_retval ())
+    {
+      region_model *dst_region_model = dst_state.m_region_model;
+      const svalue *sval
+	= dst_region_model->get_rvalue (get_retval (), &ctxt);
+      const region *ret_reg = dst_region_model->get_lvalue (lhs, &ctxt);
+      dst_region_model->set_value (ret_reg, sval, &ctxt);
+    }
+
+  if (!path_ctxt.terminate_path_p ())
+    op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty);
+}
+
+bool
+greturn_op::
+execute_for_feasibility (const exploded_edge &eedge,
+			 feasibility_state &fstate,
+			 region_model_context *ctxt,
+			 std::unique_ptr<rejected_constraint> *) const
+{
+  tree callee = eedge.m_src->get_function ()->decl;
+  tree lhs = DECL_RESULT (callee);
+
+  if (lhs && get_retval ())
+    {
+      region_model &model = fstate.get_model ();
+      const svalue *sval = model.get_rvalue (get_retval (), ctxt);
+      const region *ret_reg = model.get_lvalue (lhs, ctxt);
+      model.set_value (ret_reg, sval, ctxt);
+    }
+
+  return true;
+}
+
+void
+greturn_op::add_any_events_for_eedge (const exploded_edge &,
+				      checker_path &) const
+{
+  // No-op.
+}
+
+// class call_and_return_op : public gimple_stmt_op
+
+std::unique_ptr<operation>
+call_and_return_op::make (const gcall &call_stmt)
+{
+  if (is_special_named_call_p (call_stmt, "__analyzer_dump", 0))
+    return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state);
+  else if (is_special_named_call_p (call_stmt, "__analyzer_dump_sarif", 0))
+    return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::sarif);
+  else if (is_special_named_call_p (call_stmt, "__analyzer_dump_dot", 0))
+    return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::dot);
+  else if (is_special_named_call_p (call_stmt, "__analyzer_dump_state", 2))
+    return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state_2);
+  else if (is_setjmp_call_p (call_stmt))
+    return std::make_unique<setjmp_op> (call_stmt);
+  else if (is_longjmp_call_p (call_stmt))
+    return std::make_unique<longjmp_op> (call_stmt);
+  else if (is_cxa_throw_p (call_stmt))
+    return std::make_unique<cxa_throw_op> (call_stmt, false);
+  else if (is_cxa_rethrow_p (call_stmt))
+    return std::make_unique<cxa_throw_op> (call_stmt, true);
+
+  return std::make_unique<call_and_return_op> (call_stmt);
+}
+
+/* Resolve a function call by one of:
+
+   (a) using a call summary to add eedges to new enodes capturing
+   the states after summarized outcomes of the call
+
+   (b) adding an interprocedural_call edge, effectively "stepping into"
+   the called function, for detailed analysis of that path
+
+   (c) simulating the effect of the call, adding an eedge to a new
+   enode for the outcome of the call.  */
+
+void
+call_and_return_op::execute (operation_context &op_ctxt) const
+{
+  /* Can we turn this into an interprocedural call, and execute within
+     the called fuction?  */
+  const program_state &old_state  = op_ctxt.get_initial_state ();
+  program_state dst_state (old_state);
+  op_region_model_context ctxt (op_ctxt, dst_state);
+  ctxt.m_stmt = &get_gcall ();
+  call_details cd (get_gcall (), old_state.m_region_model, &ctxt);
+
+  /* Regardless of how we handle the call, check any known
+     preconditions.  */
+  {
+    /* Check for any preconditions if it's a known_function.  */
+    if (auto kf = maybe_get_known_function (cd))
+      kf->check_any_preconditions (cd);
+
+    /* Check for any preconditions using sm-state.  */
+    {
+      int sm_idx;
+      sm_state_map *smap;
+      FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+	{
+	  const state_machine &sm
+	    = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx);
+	  const sm_state_map *old_smap
+	    = old_state.m_checker_states[sm_idx];
+	  sm_state_map *new_smap = dst_state.m_checker_states[sm_idx];
+	  impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm,
+				   &op_ctxt.m_src_enode,
+				   &old_state, &dst_state,
+				   old_smap, new_smap, nullptr);
+	  sm.check_call_preconditions (sm_ctxt, cd);
+	}
+    }
+  }
+
+  if (tree callee_fndecl = cd.get_fndecl_for_call ())
+    {
+      // Consider using a call summary
+      if (function *called_fn = DECL_STRUCT_FUNCTION (callee_fndecl))
+	if (cgraph_edge *edge = get_any_cgraph_edge (op_ctxt))
+	  if (op_ctxt.m_eg.get_analysis_plan ().use_summary_p (edge))
+	    {
+	      per_function_data *called_fn_data
+		= op_ctxt.m_eg.get_per_function_data (called_fn);
+	      if (called_fn_data)
+		{
+		  replay_call_summaries (op_ctxt, *called_fn,
+					 *called_fn_data, &ctxt);
+		  return;
+		}
+	    }
+
+      // Do we have an entry snode for this fndecl?
+      if (auto callee_fun = DECL_STRUCT_FUNCTION (callee_fndecl))
+	if (supernode *callee_entry_snode
+	    = (op_ctxt.get_supergraph ()
+	       .get_node_for_function_entry (*callee_fun)))
+	  {
+	    const call_string *dst_call_string
+	      (op_ctxt.m_src_enode
+	       .get_point ()
+	       .get_call_string ()
+	       .push_call (op_ctxt.m_sedge, *this, *callee_fun));
+	    const program_point dst_point
+	      (callee_entry_snode, *dst_call_string);
+	    auto edge_info
+	      = std::make_unique<interprocedural_call> (get_gcall (),
+							*callee_fun);
+	    edge_info->update_state (&dst_state, nullptr, &ctxt);
+	    op_ctxt.add_outcome (dst_point, dst_state, false, nullptr,
+				 std::move (edge_info));
+	    return;
+	  }
+    }
+
+  /* Resolve intraprocedurally: execute the gcall, but using the
+     dst_state from above so that any preconditions have been applied.  */
+  gimple_stmt_op::execute_on_state (op_ctxt, std::move (dst_state));
+}
+
+cgraph_edge *
+call_and_return_op::get_any_cgraph_edge (operation_context &op_ctxt) const
+{
+  tree caller_fndecl = op_ctxt.get_initial_point ().get_fndecl ();
+  gcc_assert (caller_fndecl);
+
+  auto caller_cgnode = cgraph_node::get (caller_fndecl);
+  gcc_assert (caller_cgnode);
+  return caller_cgnode->get_edge (const_cast<gcall *> (&get_gcall ()));
+}
+
+void
+call_and_return_op::
+add_any_events_for_eedge (const exploded_edge &,
+			  checker_path &) const
+{
+}
+
+/* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it
+   to *OUT if OUT is non-NULL), and return the corresponding argument
+   at the callsite.  */
+
+tree
+call_and_return_op::get_arg_for_parm (tree callee_fndecl,
+				      tree parm_to_find,
+				      callsite_expr *out) const
+{
+  gcc_assert  (TREE_CODE (parm_to_find) == PARM_DECL);
+
+  const gcall &call_stmt = get_gcall ();
+
+  unsigned i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      if (i >= gimple_call_num_args (&call_stmt))
+	return NULL_TREE;
+      if (iter_parm == parm_to_find)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  return gimple_call_arg (&call_stmt, i);
+	}
+    }
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* Look for a use of ARG_TO_FIND as an argument at this callsite.
+   If found, return the default SSA def of the corresponding parm within
+   the callee, and if OUT is non-NULL, write the index to *OUT.
+   Only the first match is handled.  */
+
+tree
+call_and_return_op::get_parm_for_arg (tree callee_fndecl,
+				      tree arg_to_find,
+				      callsite_expr *out) const
+{
+  const gcall &call_stmt = get_gcall ();
+
+  unsigned i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      if (i >= gimple_call_num_args (&call_stmt))
+	return NULL_TREE;
+      tree param = gimple_call_arg (&call_stmt, i);
+      if (arg_to_find == param)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  return ssa_default_def (DECL_STRUCT_FUNCTION (callee_fndecl),
+				  iter_parm);
+	}
+    }
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* Map caller_expr back to an expr within the callee, or return NULL_TREE.
+   If non-NULL is returned, populate OUT.  */
+
+tree
+call_and_return_op::map_expr_from_caller_to_callee (tree callee_fndecl,
+						    tree caller_expr,
+						    callsite_expr *out) const
+{
+  /* Is it an argument (actual param)?  If so, convert to
+     parameter (formal param).  */
+  tree parm = get_parm_for_arg (callee_fndecl, caller_expr, out);
+  if (parm)
+    return parm;
+  /* Otherwise try return value.  */
+  if (caller_expr == gimple_call_lhs (&get_gcall ()))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      return DECL_RESULT (callee_fndecl);
+    }
+
+  return NULL_TREE;
+}
+
+/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
+   If non-NULL is returned, populate OUT.  */
+
+tree
+call_and_return_op::map_expr_from_callee_to_caller (tree callee_fndecl,
+						    tree callee_expr,
+						    callsite_expr *out) const
+{
+  if (callee_expr == NULL_TREE)
+    return NULL_TREE;
+
+  /* If it's a parameter (formal param), get the argument (actual param).  */
+  if (TREE_CODE (callee_expr) == PARM_DECL)
+    return get_arg_for_parm (callee_fndecl, callee_expr, out);
+
+  /* Similar for the default SSA name of the PARM_DECL.  */
+  if (TREE_CODE (callee_expr) == SSA_NAME
+      && SSA_NAME_IS_DEFAULT_DEF (callee_expr)
+      && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
+    return get_arg_for_parm (callee_fndecl, SSA_NAME_VAR (callee_expr), out);
+
+  /* Otherwise try return value.  */
+  if (callee_expr == DECL_RESULT (callee_fndecl))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      return gimple_call_lhs (&get_gcall ());
+    }
+
+  return NULL_TREE;
+}
+
+const known_function *
+call_and_return_op::maybe_get_known_function (const call_details &cd) const
+{
+  region_model_manager *mgr = cd.get_manager ();
+  known_function_manager *known_fn_mgr = mgr->get_known_function_manager ();
+
+  if (gimple_call_internal_p (&get_gcall ()))
+    return known_fn_mgr->get_internal_fn
+      (gimple_call_internal_fn (&get_gcall ()));
+
+  if (tree callee_fndecl = cd.get_fndecl_for_call ())
+    return known_fn_mgr->get_match (callee_fndecl, cd);
+
+  return nullptr;
+}
+
+void
+call_and_return_op::
+replay_call_summaries (operation_context &op_ctxt,
+		       function &called_fn,
+		       per_function_data &called_fn_data,
+		       region_model_context *ctxt) const
+{
+  logger *logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+
+  for (auto summary : called_fn_data.m_summaries)
+    {
+      gcc_assert (summary);
+      replay_call_summary (op_ctxt, called_fn, *summary, ctxt);
+    }
+}
+
+/* A concrete call_info subclass representing a replay of a call summary.  */
+
+class call_summary_edge_info : public call_info
+{
+public:
+  call_summary_edge_info (const call_details &cd,
+			  const function &called_fn,
+			  call_summary &summary,
+			  const extrinsic_state &ext_state)
+  : call_info (cd, called_fn),
+    m_called_fn (called_fn),
+    m_summary (summary),
+    m_ext_state (ext_state)
+  {}
+
+  bool update_state (program_state *state,
+		     const exploded_edge *,
+		     region_model_context *ctxt) const final override
+  {
+    /* Update STATE based on summary_end_state.  */
+    call_details cd (get_call_details (state->m_region_model, ctxt));
+    call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
+    const program_state &summary_end_state = m_summary.get_state ();
+    return state->replay_call_summary (r, summary_end_state);
+  }
+
+  bool update_model (region_model *model,
+		     const exploded_edge *,
+		     region_model_context *ctxt) const final override
+  {
+    /* Update STATE based on summary_end_state.  */
+    call_details cd (get_call_details (model, ctxt));
+    call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state);
+    const program_state &summary_end_state = m_summary.get_state ();
+    model->replay_call_summary (r, *summary_end_state.m_region_model);
+    return true;
+  }
+
+  void print_desc (pretty_printer &pp) const final override
+  {
+    pp_string (&pp, m_summary.get_desc ().get ());
+  }
+
+private:
+  const function &m_called_fn;
+  call_summary &m_summary;
+  const extrinsic_state &m_ext_state;
+};
+
+void
+call_and_return_op::
+replay_call_summary (operation_context &op_ctxt,
+		     function &called_fn,
+		     call_summary &summary,
+		     region_model_context *ctxt) const
+{
+  logger *logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+  if (logger)
+    logger->log ("using %s as summary for call to %qE from %qE",
+		 summary.get_desc ().get (),
+		 called_fn.decl,
+		 op_ctxt.get_initial_point ().get_function ()->decl);
+  const extrinsic_state &ext_state = op_ctxt.get_ext_state ();
+  const program_state &old_state = op_ctxt.get_initial_state ();
+  const program_state &summary_end_state = summary.get_state ();
+  if (logger)
+    {
+      pretty_printer *pp = logger->get_printer ();
+
+      logger->start_log_line ();
+      pp_string (pp, "callsite state: ");
+      old_state.dump_to_pp (ext_state, true, false, pp);
+      logger->end_log_line ();
+
+      logger->start_log_line ();
+      pp_string (pp, "summary end state: ");
+      summary_end_state.dump_to_pp (ext_state, true, false, pp);
+      logger->end_log_line ();
+    }
+
+  program_state new_state (old_state);
+
+  call_details cd (get_gcall (), new_state.m_region_model, ctxt);
+  call_summary_replay r (cd, called_fn, summary, ext_state);
+
+  if (new_state.replay_call_summary (r, summary_end_state))
+    op_ctxt.add_outcome
+      (op_ctxt.get_next_intraprocedural_point (),
+       new_state,
+       true,
+       nullptr,
+       std::make_unique<call_summary_edge_info> (cd,
+						 called_fn,
+						 summary,
+						 ext_state));
+}
+
+// class dump_op : public call_and_return_op
+
+void
+dump_op::execute (operation_context &op_ctxt) const
+{
+  const program_state &state = op_ctxt.get_initial_state ();
+  switch (m_dump_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case dump_kind::state:
+      /* Handle the builtin "__analyzer_dump" by dumping state
+	 to stderr.  */
+      state.dump (op_ctxt.get_ext_state (), true);
+      break;
+    case dump_kind::sarif:
+      state.dump_sarif (op_ctxt.get_ext_state ());
+      break;
+    case dump_kind::dot:
+      state.dump_dot (op_ctxt.get_ext_state ());
+      break;
+    case dump_kind::state_2:
+      {
+	program_state dst_state (state);
+	op_region_model_context ctxt (op_ctxt, dst_state);
+	dst_state.impl_call_analyzer_dump_state (get_gcall (),
+						 op_ctxt.get_ext_state (),
+						 &ctxt);
+      }
+      break;
+    }
+
+  op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (),
+		       state, false, nullptr);
+}
+
+// class setjmp_op : public call_and_return_op
+
+void
+setjmp_op::execute (operation_context &op_ctxt) const
+{
+  program_state dst_state (op_ctxt.get_initial_state ());
+  op_region_model_context ctxt (op_ctxt, dst_state);
+  dst_state.m_region_model->on_setjmp (get_gcall (),
+				       op_ctxt.m_src_enode,
+				       op_ctxt.m_sedge,
+				       &ctxt);
+  op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (),
+		       dst_state, true, nullptr);
+}
+
+void
+setjmp_op::add_any_events_for_eedge (const exploded_edge &eedge,
+				     checker_path &out_path) const
+{
+  out_path.add_event
+    (std::make_unique<setjmp_event>
+       (event_loc_info (eedge.m_src),
+	eedge.m_src,
+	get_gcall ()));
+}
+
+// class longjmp_op : public call_and_return_op
+
+void
+longjmp_op::execute (operation_context &op_ctxt) const
+{
+  program_state dst_state (op_ctxt.get_initial_state ());
+  op_region_model_context ctxt (op_ctxt, dst_state);
+  op_ctxt.m_src_enode.on_longjmp (op_ctxt.m_eg, get_gcall (), &dst_state,
+				  &ctxt);
+}
+
+// class cxa_throw_op : public call_and_return_op
+
+void
+cxa_throw_op::execute (operation_context &op_ctxt) const
+{
+  program_state dst_state (op_ctxt.get_initial_state ());
+  op_region_model_context ctxt (op_ctxt, dst_state);
+  program_point after_throw_point (op_ctxt.get_next_intraprocedural_point ());
+  op_ctxt.m_src_enode.on_throw (op_ctxt.m_eg,
+				get_gcall (),
+				after_throw_point,
+				&dst_state,
+				m_is_rethrow,
+				&ctxt);
+  // We don't continue along op_ctxt's superedge
+}
+
+// class control_flow_op : public operation
+
+void
+control_flow_op::
+walk_load_store_addr_ops (void *data,
+			  walk_stmt_load_store_addr_fn load_cb,
+			  walk_stmt_load_store_addr_fn store_cb,
+			  walk_stmt_load_store_addr_fn addr_cb) const
+{
+  walk_stmt_load_store_addr_ops (const_cast <gimple *> (&m_ctrlflow_stmt),
+				 data,
+				 load_cb, store_cb, addr_cb);
+}
+
+void
+control_flow_op::add_any_events_for_eedge (const exploded_edge &eedge,
+					   checker_path &out_path) const
+{
+  out_path.add_event
+    (std::make_unique<start_cfg_edge_event> (eedge,
+					     event_loc_info (eedge.m_src),
+					     this));
+  out_path.add_event
+     (std::make_unique<end_cfg_edge_event> (eedge,
+					    event_loc_info (eedge.m_dest),
+					    this));
+}
+
+/* Attempt to generate a description of any condition that holds at this edge.
+
+   The intent is to make the user-facing messages more clear, especially for
+   cases where there's a single or double-negative, such as
+   when describing the false branch of an inverted condition.
+
+   For example, rather than printing just:
+
+      |  if (!ptr)
+      |     ~
+      |     |
+      |     (1) following 'false' branch...
+
+   it's clearer to spell out the condition that holds:
+
+      |  if (!ptr)
+      |     ~
+      |     |
+      |     (1) following 'false' branch (when 'ptr' is non-NULL)...
+                                          ^^^^^^^^^^^^^^^^^^^^^^
+
+   In the above example, this function would generate the highlighted
+   string: "when 'ptr' is non-NULL".
+
+   If the edge is not a condition, or it's not clear that a description of
+   the condition would be helpful to the user, return NULL.  */
+
+label_text
+control_flow_op::maybe_describe_condition (bool ) const
+{
+  return label_text::borrow (nullptr);
+}
+
+void
+control_flow_op::execute (operation_context &op_ctxt) const
+{
+  auto logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+
+  program_state dst_state (op_ctxt.get_initial_state ());
+  op_region_model_context ctxt (op_ctxt, dst_state);
+  if (apply_constraints (&op_ctxt.m_sedge,
+			 *dst_state.m_region_model,
+			 &ctxt,
+			 nullptr))
+    {
+      bool unknown_side_effects;
+      handle_on_stmt_for_state_machines (op_ctxt,
+					 dst_state,
+					 nullptr,
+					 unknown_side_effects,
+					 m_ctrlflow_stmt);
+
+      if (!ctxt.terminate_path_p ())
+	{
+	  auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+	  op_ctxt.add_outcome (dst_point, dst_state, false, nullptr);
+	}
+    }
+}
+
+bool
+control_flow_op::
+execute_for_feasibility (const exploded_edge &eedge,
+			 feasibility_state &fstate,
+			 region_model_context *ctxt,
+			 std::unique_ptr<rejected_constraint> *out_rc) const
+{
+  gcc_assert (eedge.m_sedge);
+  return apply_constraints (eedge.m_sedge,
+			    fstate.get_model (),
+			    ctxt,
+			    out_rc);
+}
+
+// class gcond_edge_op : public control_flow_op
+
+gcond_edge_op::gcond_edge_op (::edge cfg_edge,
+			      const gcond &cond_stmt)
+: control_flow_op (kind::cond_edge, cfg_edge, cond_stmt),
+  m_true_value (get_flags () & EDGE_TRUE_VALUE)
+{
+  /* Exactly one of EDGE_TRUE_VALUE and EDGE_FALSE_VALUE must
+     be set on CFG_EDGE.  */
+  gcc_assert (static_cast<bool> (get_flags () & EDGE_TRUE_VALUE)
+	      ^ static_cast<bool> (get_flags () & EDGE_FALSE_VALUE));
+}
+
+void
+gcond_edge_op::print_as_edge_label (pretty_printer *pp,
+				    bool user_facing) const
+{
+  if (!user_facing)
+    pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0);
+
+  if (m_true_value)
+    pp_printf (pp, "true");
+  else
+    pp_printf (pp, "false");
+}
+
+label_text
+gcond_edge_op::maybe_describe_condition (bool can_colorize) const
+{
+  const gcond &cond_stmt = get_gcond ();
+  enum tree_code op = gimple_cond_code (&cond_stmt);
+  tree lhs = gimple_cond_lhs (&cond_stmt);
+  tree rhs = gimple_cond_rhs (&cond_stmt);
+  if (!m_true_value)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  return maybe_describe_condition (can_colorize,
+				   lhs, op, rhs);
+}
+
+/* Subroutine of gcond_edge_op::maybe_describe_condition above.
+
+   Attempt to generate a user-facing description of the condition
+   LHS OP RHS, but only if it is likely to make it easier for the
+   user to understand a condition.  */
+
+label_text
+gcond_edge_op::maybe_describe_condition (bool can_colorize,
+					 tree lhs,
+					 enum tree_code op,
+					 tree rhs)
+{
+  /* In theory we could just build a tree via
+       fold_build2 (op, boolean_type_node, lhs, rhs)
+     and print it with %qE on it, but this leads to warts such as
+     parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'.  */
+
+  /* Special-case: describe testing the result of strcmp, as figuring
+     out what the "true" or "false" path is can be confusing to the user.  */
+  if (TREE_CODE (lhs) == SSA_NAME
+      && zerop (rhs))
+    {
+      if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs)))
+	if (is_special_named_call_p (*call, "strcmp", 2))
+	  {
+	    if (op == EQ_EXPR)
+	      return label_text::borrow ("when the strings are equal");
+	    if (op == NE_EXPR)
+	      return label_text::borrow ("when the strings are non-equal");
+	  }
+    }
+
+  /* Only attempt to generate text for sufficiently simple expressions.  */
+  if (!should_print_expr_p (lhs))
+    return label_text::borrow (nullptr);
+  if (!should_print_expr_p (rhs))
+    return label_text::borrow (nullptr);
+
+  /* Special cases for pointer comparisons against NULL.  */
+  if (POINTER_TYPE_P (TREE_TYPE (lhs))
+      && POINTER_TYPE_P (TREE_TYPE (rhs))
+      && zerop (rhs))
+    {
+      if (op == EQ_EXPR)
+	return make_label_text (can_colorize, "when %qE is NULL",
+				lhs);
+      if (op == NE_EXPR)
+	return make_label_text (can_colorize, "when %qE is non-NULL",
+				lhs);
+    }
+
+  return make_label_text (can_colorize, "when %<%E %s %E%>",
+			  lhs, op_symbol_code (op), rhs);
+}
+
+/* Subroutine of maybe_describe_condition.
+
+   Return true if EXPR is we will get suitable user-facing output
+   from %E on it.  */
+
+bool
+gcond_edge_op::should_print_expr_p (tree expr)
+{
+  if (TREE_CODE (expr) == SSA_NAME)
+    {
+      if (SSA_NAME_VAR (expr))
+	return should_print_expr_p (SSA_NAME_VAR (expr));
+      else
+	return false;
+    }
+
+  if (DECL_P (expr))
+    return true;
+
+  if (CONSTANT_CLASS_P (expr))
+    return true;
+
+  return false;
+}
+
+bool
+gcond_edge_op::
+apply_constraints (const superedge *,
+		   region_model &model,
+		   region_model_context *ctxt,
+		   std::unique_ptr<rejected_constraint> *out) const
+{
+  const gcond &cond_stmt = get_gcond ();
+  enum tree_code op = gimple_cond_code (&cond_stmt);
+  tree lhs = gimple_cond_lhs (&cond_stmt);
+  tree rhs = gimple_cond_rhs (&cond_stmt);
+  if (!m_true_value)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  return model.add_constraint (lhs, op, rhs, ctxt, out);
+}
+
+// class ggoto_edge_op : public control_flow_op
+
+ggoto_edge_op::ggoto_edge_op (::edge cfg_edge,
+			      const ggoto &goto_stmt,
+			      tree dst_label)
+: control_flow_op (kind::goto_edge, cfg_edge, goto_stmt),
+  m_dst_label (dst_label)
+{
+}
+
+void
+ggoto_edge_op::print_as_edge_label (pretty_printer *pp,
+				    bool user_facing) const
+{
+  if (!user_facing)
+    pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0);
+
+  if (m_dst_label)
+    pp_printf (pp, "%qD", m_dst_label);
+}
+
+label_text
+ggoto_edge_op::maybe_describe_condition (bool) const
+{
+  return label_text::borrow ("");
+}
+
+bool
+ggoto_edge_op::
+apply_constraints (const superedge *,
+		   region_model &model,
+		   region_model_context *ctxt,
+		   std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+  const ggoto &goto_stmt = get_ggoto ();
+  tree dest = gimple_goto_dest (&goto_stmt);
+  const svalue *dest_sval = model.get_rvalue (dest, ctxt);
+
+  /* If we know we were jumping to a specific label.  */
+  if (m_dst_label)
+    {
+      auto mgr = model.get_manager ();
+      const label_region *dst_label_reg
+	= mgr->get_region_for_label (m_dst_label);
+      const svalue *dst_label_ptr
+	= mgr->get_ptr_svalue (ptr_type_node, dst_label_reg);
+
+      if (!model.add_constraint (dest_sval, EQ_EXPR, dst_label_ptr, ctxt))
+	return false;
+    }
+
+  return true;
+}
+
+// class switch_case_op : public control_flow_op
+
+switch_case_op::switch_case_op (function &fun,
+				::edge cfg_edge,
+				const gswitch &switch_stmt,
+				bounded_ranges_manager &mgr)
+: control_flow_op (kind::switch_edge, cfg_edge, switch_stmt)
+{
+  /* Populate m_case_labels with all cases which go to DST.  */
+  for (unsigned i = 0; i < gimple_switch_num_labels (&switch_stmt); i++)
+    {
+      tree case_ = gimple_switch_label (&switch_stmt, i);
+      basic_block bb = label_to_block (&fun,
+				       CASE_LABEL (case_));
+      if (bb == cfg_edge->dest)
+	m_case_labels.push_back (case_);
+    }
+
+  auto_vec <const bounded_ranges *> case_ranges_vec
+    (gimple_switch_num_labels (&switch_stmt));
+  for (auto case_label : m_case_labels)
+    {
+      /* Get the ranges for this case label.  */
+      const bounded_ranges *case_ranges
+	= mgr.make_case_label_ranges (&switch_stmt, case_label);
+      case_ranges_vec.quick_push (case_ranges);
+    }
+
+  m_all_cases_ranges = mgr.get_or_create_union (case_ranges_vec);
+}
+
+/* Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP.  */
+
+void
+switch_case_op::print_as_edge_label (pretty_printer *pp,
+				     bool user_facing) const
+{
+  if (user_facing)
+    {
+      for (unsigned i = 0; i < m_case_labels.size (); ++i)
+	{
+	  if (i > 0)
+	    pp_string (pp, ", ");
+	  tree case_label = m_case_labels[i];
+	  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+	  tree lower_bound = CASE_LOW (case_label);
+	  tree upper_bound = CASE_HIGH (case_label);
+	  if (lower_bound)
+	    {
+	      pp_printf (pp, "case ");
+	      dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+	      if (upper_bound)
+		{
+		  pp_printf (pp, " ... ");
+		  dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
+				     false);
+		}
+	      pp_printf (pp, ":");
+	    }
+	  else
+	    pp_printf (pp, "default:");
+	}
+    }
+  else
+    {
+      pp_character (pp, '{');
+      for (unsigned i = 0; i < m_case_labels.size (); ++i)
+	{
+	  if (i > 0)
+	    pp_string (pp, ", ");
+	  tree case_label = m_case_labels[i];
+	  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+	  tree lower_bound = CASE_LOW (case_label);
+	  tree upper_bound = CASE_HIGH (case_label);
+	  if (lower_bound)
+	    {
+	      if (upper_bound)
+		{
+		  pp_character (pp, '[');
+		  dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0,
+				     false);
+		  pp_string (pp, ", ");
+		  dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
+				     false);
+		  pp_character (pp, ']');
+		}
+	      else
+		dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+	    }
+	  else
+	    pp_printf (pp, "default");
+	}
+      pp_character (pp, '}');
+      if (implicitly_created_default_p ())
+	{
+	  pp_string (pp, " IMPLICITLY CREATED");
+	}
+    }
+}
+
+/* Return true iff SWITCH_STMT has a non-default label that contains
+   INT_CST.  */
+
+static bool
+has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst)
+{
+  /* We expect the initial label to be the default; skip it.  */
+  gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL_TREE);
+  unsigned min_idx = 1;
+  unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1;
+
+  /* Binary search: try to find the label containing INT_CST.
+     This requires the cases to be sorted by CASE_LOW (done by the
+     gimplifier).  */
+  while (max_idx >= min_idx)
+    {
+      unsigned case_idx = (min_idx + max_idx) / 2;
+      tree label =  gimple_switch_label (switch_stmt, case_idx);
+      tree low = CASE_LOW (label);
+      gcc_assert (low);
+      tree high = CASE_HIGH (label);
+      if (!high)
+	high = low;
+      if (tree_int_cst_compare (int_cst, low) < 0)
+	{
+	  /* INT_CST is below the range of this label.  */
+	  gcc_assert (case_idx > 0);
+	  max_idx = case_idx - 1;
+	}
+      else if (tree_int_cst_compare (int_cst, high) > 0)
+	{
+	  /* INT_CST is above the range of this case.  */
+	  min_idx = case_idx + 1;
+	}
+      else
+	/* This case contains INT_CST.  */
+	return true;
+    }
+  /* Not found.  */
+  return false;
+}
+
+/* Return true iff SWITCH_STMT (which must be on an enum value)
+   has nondefault cases handling all values in the enum.  */
+
+static bool
+has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt,
+					    tree type)
+{
+  gcc_assert (switch_stmt);
+  gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE);
+
+  for (tree enum_val_iter = TYPE_VALUES (type);
+       enum_val_iter;
+       enum_val_iter = TREE_CHAIN (enum_val_iter))
+    {
+      tree enum_val = TREE_VALUE (enum_val_iter);
+      gcc_assert (TREE_CODE (enum_val) == CONST_DECL);
+      gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST);
+      if (!has_nondefault_case_for_value_p (switch_stmt,
+					    DECL_INITIAL (enum_val)))
+	return false;
+    }
+  return true;
+}
+
+/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
+   for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).
+   When returning false, if OUT is non-NULL, write a new rejected_constraint
+   to it.  */
+
+bool
+switch_case_op::
+apply_constraints (const superedge *,
+		   region_model &model,
+		   region_model_context *ctxt,
+		   std::unique_ptr<rejected_constraint> *out) const
+{
+  const gswitch *switch_stmt = &get_gswitch ();
+  tree index  = gimple_switch_index (switch_stmt);
+  const svalue *index_sval = model.get_rvalue (index, ctxt);
+  bool check_index_type = true;
+
+  /* With -fshort-enum, there may be a type cast.  */
+  if (ctxt && index_sval->get_kind () == SK_UNARYOP
+      && TREE_CODE (index_sval->get_type ()) == INTEGER_TYPE)
+    {
+      const unaryop_svalue *unaryop = as_a <const unaryop_svalue *> (index_sval);
+      if (unaryop->get_op () == NOP_EXPR
+	  && is_a <const initial_svalue *> (unaryop->get_arg ()))
+	if (const initial_svalue *initvalop = (as_a <const initial_svalue *>
+					       (unaryop->get_arg ())))
+	  if (initvalop->get_type ()
+	      && TREE_CODE (initvalop->get_type ()) == ENUMERAL_TYPE)
+	    {
+	      index_sval = initvalop;
+	      check_index_type = false;
+	    }
+    }
+
+  /* If we're switching based on an enum type, assume that the user is only
+     working with values from the enum.  Hence if this is an
+     implicitly-created "default", assume it doesn't get followed.
+     This fixes numerous "uninitialized" false positives where we otherwise
+     consider jumping past the initialization cases.  */
+
+  if (/* Don't check during feasibility-checking (when ctxt is NULL).  */
+      ctxt
+      /* Must be an enum value.  */
+      && index_sval->get_type ()
+      && (!check_index_type
+	  || TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE)
+      && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE
+      /* If we have a constant, then we can check it directly.  */
+      && index_sval->get_kind () != SK_CONSTANT
+      && implicitly_created_default_p ()
+      && has_nondefault_cases_for_all_enum_values_p (switch_stmt,
+						     index_sval->get_type ())
+      /* Don't do this if there's a chance that the index is
+	 attacker-controlled.  */
+      && !ctxt->possibly_tainted_p (index_sval))
+    {
+      if (out)
+	*out = std::make_unique <rejected_default_case> (model);
+      return false;
+    }
+
+  bool sat
+    = model.get_constraints ()->add_bounded_ranges (index_sval,
+						    m_all_cases_ranges);
+  if (!sat && out)
+    *out = std::make_unique <rejected_ranges_constraint>
+      (model, index, m_all_cases_ranges);
+  if (sat && ctxt && !m_all_cases_ranges->empty_p ())
+    ctxt->on_bounded_ranges (*index_sval, *m_all_cases_ranges);
+  return sat;
+}
+
+/* Return true iff this op's edge is purely for an
+   implicitly-created "default".  */
+
+bool
+switch_case_op::implicitly_created_default_p () const
+{
+  if (m_case_labels.size () != 1)
+    return false;
+
+  tree case_label = m_case_labels[0];
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  if (CASE_LOW (case_label))
+    return false;
+
+  /* We have a single "default" case.
+     Assume that it was implicitly created if it has UNKNOWN_LOCATION.  */
+  return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION;
+}
+
+/* Given an ERT_TRY region, get the eh_catch corresponding to
+   the label of DST_SNODE, if any.  */
+
+static eh_catch
+get_catch (eh_region eh_reg, supernode *dst_snode)
+{
+  gcc_assert (eh_reg->type == ERT_TRY);
+
+  tree dst_snode_label = dst_snode->get_label ();
+  if (!dst_snode_label)
+    return nullptr;
+
+  for (eh_catch iter = eh_reg->u.eh_try.first_catch;
+       iter;
+       iter = iter->next_catch)
+    if (iter->label == dst_snode_label)
+      return iter;
+
+  return nullptr;
+}
+
+class rejected_eh_dispatch : public rejected_constraint
+{
+public:
+  rejected_eh_dispatch (const region_model &model)
+  : rejected_constraint (model)
+  {}
+
+  void dump_to_pp (pretty_printer *pp) const final override
+  {
+    pp_printf (pp, "rejected_eh_dispatch");
+  }
+};
+
+static bool
+exception_matches_type_p (tree exception_type,
+			  tree catch_type)
+{
+  if (catch_type == exception_type)
+    return true;
+
+  /* TODO (PR analyzer/119697): we should also handle subclasses etc;
+     see the rules in https://en.cppreference.com/w/cpp/language/catch
+
+     It looks like we should be calling (or emulating)
+     can_convert_eh from the C++ FE, but that's specific to the C++ FE.  */
+
+  return false;
+}
+
+static bool
+matches_any_exception_type_p (eh_catch ehc, tree exception_type)
+{
+  if (ehc->type_list == NULL_TREE)
+    /* All exceptions are caught here.  */
+    return true;
+
+  for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
+    if (exception_matches_type_p (TREE_VALUE (iter),
+				  exception_type))
+      return true;
+  return false;
+}
+
+// class eh_dispatch_edge_op : public control_flow_op
+
+std::unique_ptr<eh_dispatch_edge_op>
+eh_dispatch_edge_op::make (supernode *src_snode,
+			   supernode *dst_snode,
+			   ::edge cfg_edge,
+			   const geh_dispatch &eh_dispatch_stmt)
+{
+  const eh_status *eh = src_snode->get_function ()->eh;
+  gcc_assert (eh);
+  int region_idx = gimple_eh_dispatch_region (&eh_dispatch_stmt);
+  gcc_assert (region_idx > 0);
+  gcc_assert ((*eh->region_array)[region_idx]);
+  eh_region eh_reg = (*eh->region_array)[region_idx];
+  gcc_assert (eh_reg);
+  switch (eh_reg->type)
+    {
+    default:
+      gcc_unreachable ();
+    case ERT_CLEANUP:
+      // TODO
+      gcc_unreachable ();
+      break;
+    case ERT_TRY:
+      {
+	eh_catch ehc = get_catch (eh_reg, dst_snode);
+	return std::make_unique<eh_dispatch_try_edge_op>
+	  (src_snode, dst_snode,
+	   cfg_edge, eh_dispatch_stmt,
+	   eh_reg, ehc);
+      }
+      break;
+    case ERT_ALLOWED_EXCEPTIONS:
+      return std::make_unique<eh_dispatch_allowed_edge_op>
+	(src_snode, dst_snode,
+	 cfg_edge, eh_dispatch_stmt,
+	 eh_reg);
+      break;
+    case ERT_MUST_NOT_THROW:
+      // TODO
+      gcc_unreachable ();
+      break;
+    }
+}
+
+eh_dispatch_edge_op::
+eh_dispatch_edge_op (supernode *src_snode,
+		     supernode *dst_snode,
+		     enum kind kind_,
+		     ::edge cfg_edge,
+		     const geh_dispatch &geh_dispatch_stmt,
+		     eh_region eh_reg)
+: control_flow_op (kind_, cfg_edge, geh_dispatch_stmt),
+  m_src_snode (src_snode),
+  m_dst_snode (dst_snode),
+  m_eh_region (eh_reg)
+{
+}
+
+bool
+eh_dispatch_edge_op::
+apply_constraints (const superedge *sedge,
+		   region_model &model,
+		   region_model_context *ctxt,
+		   std::unique_ptr<rejected_constraint> *out) const
+{
+  const exception_node *current_node = model.get_current_thrown_exception ();
+
+  if (!current_node)
+    return false;
+
+  gcc_assert (current_node);
+  tree curr_exception_type = current_node->maybe_get_type ();
+  if (!curr_exception_type)
+    /* We don't know the specific type.  */
+    return true;
+
+  return apply_eh_constraints (sedge, model, ctxt, curr_exception_type, out);
+}
+
+// class eh_dispatch_try_edge_op : public eh_dispatch_edge_op
+
+eh_dispatch_try_edge_op::
+eh_dispatch_try_edge_op (supernode *src_snode,
+			 supernode *dst_snode,
+			 ::edge cfg_edge,
+			 const geh_dispatch &geh_dispatch_stmt,
+			 eh_region eh_reg,
+			 eh_catch ehc)
+: eh_dispatch_edge_op (src_snode, dst_snode,
+		       kind::eh_dispatch_try_edge,
+		       cfg_edge, geh_dispatch_stmt, eh_reg),
+  m_eh_catch (ehc)
+{
+  gcc_assert (eh_reg->type == ERT_TRY);
+}
+
+void
+eh_dispatch_try_edge_op::print_as_edge_label (pretty_printer *pp,
+					      bool user_facing) const
+{
+  if (!user_facing)
+    pp_string (pp, "ERT_TRY: ");
+  if (m_eh_catch)
+    {
+      bool first = true;
+      for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter))
+	{
+	  if (!first)
+	    pp_string (pp, ", ");
+	  pp_printf (pp, "on catch %qT", TREE_VALUE (iter));
+	  first = false;
+	}
+    }
+  else
+    pp_string (pp, "on uncaught exception");
+}
+
+void
+eh_dispatch_try_edge_op::add_any_events_for_eedge (const exploded_edge &eedge,
+						   checker_path &out_path) const
+{
+  if (m_eh_catch)
+    {
+      const region_model *model = eedge.m_src->get_state ().m_region_model;
+      auto curr_thrown_exception_node
+	= model->get_current_thrown_exception ();
+      gcc_assert (curr_thrown_exception_node);
+      tree type = curr_thrown_exception_node->maybe_get_type ();
+      out_path.add_event
+	(std::make_unique<catch_cfg_edge_event>
+	 (eedge,
+	  event_loc_info (eedge.m_dest),
+	  *this,
+	  type));
+    }
+  else
+    {
+      /* We have the "uncaught exception" sedge, from eh_dispatch
+	 to a block containing resx.
+	 Don't add any events for this, so that we can consolidate
+	 adjacent stack unwinding events.  */
+    }
+}
+
+bool
+eh_dispatch_try_edge_op::
+apply_eh_constraints (const superedge *sedge,
+		      region_model &model,
+		      region_model_context */*ctxt*/,
+		      tree exception_type,
+		      std::unique_ptr<rejected_constraint> *out) const
+{
+  /* TODO: can we rely on this ordering?
+     or do we need to iterate through prev_catch ?  */
+  /* The exception must not match any of the previous edges.  */
+  for (auto sibling_sedge : get_src_snode ()->m_succs)
+    {
+      if (sibling_sedge == sedge)
+	break;
+
+      const eh_dispatch_try_edge_op *sibling_edge_op
+	= (const eh_dispatch_try_edge_op *)sibling_sedge->get_op ();
+      if (eh_catch ehc = sibling_edge_op->m_eh_catch)
+	if (matches_any_exception_type_p (ehc, exception_type))
+	  {
+	    /* The earlier sibling matches, so the "unhandled" edge is
+	       not taken.  */
+	    if (out)
+	      *out = std::make_unique<rejected_eh_dispatch> (model);
+	    return false;
+	  }
+    }
+
+  if (eh_catch ehc = m_eh_catch)
+    {
+      /* We have an edge that tried to match one or more types.  */
+
+      /* The exception must not match any of the previous edges.  */
+
+      /* It must match this type.  */
+      if (matches_any_exception_type_p (ehc, exception_type))
+	return true;
+      else
+	{
+	  /* Exception type doesn't match.  */
+	  if (out)
+	    *out = std::make_unique<rejected_eh_dispatch> (model);
+	  return false;
+	}
+    }
+  else
+    {
+      /* This is the "unhandled exception" edge.
+	 If we get here then no sibling edges matched;
+	 we will follow this edge.  */
+      return true;
+    }
+}
+
+// class eh_dispatch_allowed_edge_op : public eh_dispatch_edge_op
+
+eh_dispatch_allowed_edge_op::
+eh_dispatch_allowed_edge_op (supernode *src_snode,
+			     supernode *dst_snode,
+			     ::edge cfg_edge,
+			     const geh_dispatch &geh_dispatch_stmt,
+			     eh_region eh_reg)
+: eh_dispatch_edge_op (src_snode, dst_snode,
+		       kind::eh_dispatch_try_edge,
+		       cfg_edge, geh_dispatch_stmt, eh_reg)
+{
+  gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS);
+
+  /* We expect two sibling out-edges at an eh_dispatch from such a region:
+
+     - one to a bb without a gimple label, with a resx,
+     for exceptions of expected types
+
+     - one to a bb with a gimple label, with a call to __cxa_unexpected,
+     for exceptions of unexpected types.
+
+     Set m_kind for this edge accordingly.  */
+  gcc_assert (cfg_edge->src->succs->length () == 2);
+  tree label_for_unexpected_exceptions = eh_reg->u.allowed.label;
+  tree label_for_dest_enode = dst_snode->get_label ();
+  if (label_for_dest_enode == label_for_unexpected_exceptions)
+    m_kind = eh_kind::unexpected;
+  else
+    {
+      gcc_assert (label_for_dest_enode == nullptr);
+      m_kind = eh_kind::expected;
+    }
+}
+
+void
+eh_dispatch_allowed_edge_op::print_as_edge_label (pretty_printer *pp,
+						  bool user_facing) const
+{
+  if (!user_facing)
+    {
+      switch (m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case eh_kind::expected:
+	  pp_string (pp, "expected: ");
+	  break;
+	case eh_kind::unexpected:
+	  pp_string (pp, "unexpected: ");
+	  break;
+	}
+      pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: ");
+      eh_region eh_reg = get_eh_region ();
+      bool first = true;
+      for (tree iter = eh_reg->u.allowed.type_list; iter;
+	   iter = TREE_CHAIN (iter))
+	{
+	  if (!first)
+	    pp_string (pp, ", ");
+	  pp_printf (pp, "%qT", TREE_VALUE (iter));
+	  first = false;
+	}
+    }
+}
+
+bool
+eh_dispatch_allowed_edge_op::
+apply_eh_constraints (const superedge *,
+		      region_model &model,
+		      region_model_context */*ctxt*/,
+		      tree exception_type,
+		      std::unique_ptr<rejected_constraint> *out) const
+{
+  auto curr_thrown_exception_node = model.get_current_thrown_exception ();
+  gcc_assert (curr_thrown_exception_node);
+  tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
+  eh_region eh_reg = get_eh_region ();
+  tree type_list = eh_reg->u.allowed.type_list;
+
+  switch (get_eh_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case eh_kind::expected:
+      if (!curr_exception_type)
+	{
+	  /* We don't know the specific type;
+	     assume we have one of an expected type.  */
+	  return true;
+	}
+      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+	if (exception_matches_type_p (TREE_VALUE (iter),
+				      exception_type))
+	  return true;
+      if (out)
+	*out = std::make_unique<rejected_eh_dispatch> (model);
+      return false;
+
+    case eh_kind::unexpected:
+      if (!curr_exception_type)
+	{
+	  /* We don't know the specific type;
+	     assume we don't have one of an expected type.  */
+	  if (out)
+	    *out = std::make_unique<rejected_eh_dispatch> (model);
+	  return false;
+	}
+      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+	if (exception_matches_type_p (TREE_VALUE (iter),
+				      exception_type))
+	  {
+	    if (out)
+	      *out = std::make_unique<rejected_eh_dispatch> (model);
+	    return false;
+	  }
+      return true;
+    }
+}
+
+// class phis_for_edge_op : public operation
+
+std::unique_ptr<operation>
+phis_for_edge_op::maybe_make (::edge cfg_in_edge)
+{
+  std::vector<pair> pairs = get_pairs_for_phi_along_in_edge (cfg_in_edge);
+  if (pairs.empty ())
+    return nullptr;
+
+  return std::make_unique <phis_for_edge_op> (std::move (pairs),
+					      cfg_in_edge);
+}
+
+phis_for_edge_op::phis_for_edge_op (std::vector<pair> &&pairs,
+				    ::edge cfg_in_edge)
+: operation (kind::phis),
+  m_pairs (std::move (pairs)),
+  m_cfg_in_edge (cfg_in_edge)
+{
+}
+
+std::vector<phis_for_edge_op::pair>
+phis_for_edge_op::get_pairs_for_phi_along_in_edge (::edge cfg_in_edge)
+{
+  std::vector<pair> result;
+
+  const size_t phi_arg_idx = cfg_in_edge->dest_idx;
+  for (gphi_iterator gpi = gsi_start_phis (cfg_in_edge->dest);
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi * const phi = gpi.phi ();
+      tree dst = gimple_phi_result (phi);
+
+      /* We don't bother tracking the .MEM SSA names.  */
+      if (tree var = SSA_NAME_VAR (dst))
+	if (TREE_CODE (var) == VAR_DECL)
+	  if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	    continue;
+
+      tree src = gimple_phi_arg_def (phi, phi_arg_idx);
+
+      result.push_back ({dst, src});
+    }
+
+  return result;
+}
+
+void
+phis_for_edge_op::print_as_edge_label (pretty_printer *pp,
+				       bool ) const
+{
+  pp_printf (pp, "PHI(");
+  bool first = true;
+  for (auto &p : m_pairs)
+    {
+      if (first)
+	first = false;
+      else
+	pp_string (pp, ", ");
+
+      pp_printf (pp, "%E = %E", p.m_dst, p.m_src);
+    }
+  pp_printf (pp, ");");
+}
+
+void
+phis_for_edge_op::
+walk_load_store_addr_ops (void */*data*/ ,
+			  walk_stmt_load_store_addr_fn /*load_cb*/,
+			  walk_stmt_load_store_addr_fn /*store_cb*/,
+			  walk_stmt_load_store_addr_fn /*addr_cb*/) const
+{
+}
+
+bool
+phis_for_edge_op::defines_ssa_name_p (const_tree ssa_name) const
+{
+  for (auto &p : m_pairs)
+    if (p.m_dst == ssa_name)
+      return true;
+  return false;
+}
+
+void
+phis_for_edge_op::execute (operation_context &op_ctxt) const
+{
+  auto logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+
+  auto dst_point (op_ctxt.get_next_intraprocedural_point ());
+
+  const program_state &src_state (op_ctxt.get_initial_state ());
+  program_state dst_state (src_state);
+
+  impl_path_context path_ctxt (&dst_state, logger);
+  uncertainty_t uncertainty;
+  impl_region_model_context ctxt (op_ctxt.m_eg,
+				  &op_ctxt.m_src_enode,
+
+				  /* TODO: should we be getting the ECs from the
+				     old state, rather than the new?  */
+				  &op_ctxt.get_initial_state (),
+				  &dst_state,
+				  &uncertainty,
+				  &path_ctxt,
+				  nullptr,
+				  nullptr);
+
+  update_state (src_state, dst_state, &ctxt);
+
+  op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty);
+}
+
+void
+phis_for_edge_op::update_state (const program_state &src_state,
+				program_state &dst_state,
+				region_model_context *ctxt) const
+{
+  const region_model &src_model = *src_state.m_region_model;
+  region_model &dst_model = *dst_state.m_region_model;
+
+  hash_set<const svalue *> svals_changing_meaning;
+
+  /* Get state from src_state so that all of the phi stmts for an edge
+     are effectively handled simultaneously.  */
+  for (auto &p : m_pairs)
+    {
+      const svalue *src_sval = src_model.get_rvalue (p.m_src, nullptr);
+      const region *dst_reg = src_model.get_lvalue (p.m_dst, nullptr);
+
+      const svalue *old_sval = src_model.get_rvalue (p.m_dst, nullptr);
+      if (old_sval->get_kind () == SK_WIDENING)
+	svals_changing_meaning.add (old_sval);
+
+      dst_model.set_value (dst_reg, src_sval, ctxt);
+    }
+
+ for (auto iter : svals_changing_meaning)
+   dst_model.get_constraints ()->purge_state_involving (iter);
+}
+
+bool
+phis_for_edge_op::
+execute_for_feasibility (const exploded_edge &eedge,
+			 feasibility_state &fstate,
+			 region_model_context *ctxt,
+			 std::unique_ptr<rejected_constraint> */*out_rc*/) const
+{
+  hash_set<const svalue *> svals_changing_meaning;
+  /* Get state from src_state so that all of the phi stmts for an edge
+     are effectively handled simultaneously.  */
+  region_model &model = fstate.get_model ();
+  region_model src_model (model);
+  for (auto &p : m_pairs)
+    {
+      const svalue *src_sval = src_model.get_rvalue (p.m_src, ctxt);
+      const region *dst_reg = model.get_lvalue (p.m_dst, ctxt);
+
+      const svalue *sval = model.get_rvalue (p.m_dst, ctxt);
+      if (sval->get_kind () == SK_WIDENING)
+	svals_changing_meaning.add (sval);
+
+      model.set_value (dst_reg, src_sval, ctxt);
+    }
+
+  for (auto iter : svals_changing_meaning)
+    model.get_constraints ()->purge_state_involving (iter);
+
+  {
+    /* If we've entering an snode that we've already visited on this
+       epath, then we need do fix things up for loops; see the
+       comment for store::loop_replay_fixup.
+       Perhaps we should probably also verify the callstring,
+       and track program_points,  but hopefully doing it by supernode
+       is good enough.  */
+    const exploded_node &dst_enode = *eedge.m_dest;
+    const unsigned dst_snode_idx = dst_enode.get_supernode ()->m_id;
+    if (bitmap_bit_p (fstate.get_snodes_visited (), dst_snode_idx))
+      model.loop_replay_fixup (dst_enode.get_state ().m_region_model);
+  }
+ 
+  return true;
+}
+
+void
+phis_for_edge_op::
+update_state_for_bulk_merger (const program_state &src_state,
+			      program_state &dst_state) const
+{
+  update_state (src_state, dst_state, nullptr);
+}
+
+void
+phis_for_edge_op::add_any_events_for_eedge (const exploded_edge &,
+					    checker_path &) const
+{
+  // No-op
+}
+
+// class resx_op : public gimple_stmt_op
+
+void
+resx_op::execute (operation_context &op_ctxt) const
+{
+  auto logger = op_ctxt.get_logger ();
+  LOG_SCOPE (logger);
+
+  program_point dst_point (op_ctxt.get_next_intraprocedural_point ());
+  program_state dst_state (op_ctxt.get_initial_state ());
+  op_region_model_context ctxt (op_ctxt, dst_state);
+
+  if (exploded_node *dst_enode
+      = op_ctxt.m_eg.get_or_create_node (dst_point, dst_state,
+					 &op_ctxt.m_src_enode,
+					 // Don't add to worklist:
+					 false))
+    {
+      op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode,
+			     dst_enode,
+			     &op_ctxt.m_sedge,
+			     false,
+			     nullptr);
+      /* Try to adding eedges and enodes that unwind to the next
+	 eh_dispatch statement, if any.
+	 Only the final enode is added to the worklist.  */
+      op_ctxt.m_eg.unwind_from_exception (*dst_enode,
+					  nullptr,
+					  &ctxt);
+    }
+}
+
+void
+resx_op::add_any_events_for_eedge (const exploded_edge &,
+				   checker_path &) const
+{
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/ops.h b/gcc/analyzer/ops.h
new file mode 100644
index 000000000000..8681e0266c49
--- /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 <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_OPS_H
+#define GCC_ANALYZER_OPS_H
+
+#include "except.h"
+#include "gimple-walk.h"
+
+namespace ana {
+
+class operation;
+  class control_flow_op;
+  class call_and_return_op;
+  class phis_for_edge_op;
+
+class callsite_expr;
+
+struct operation_context
+{
+  operation_context (exploded_graph &eg,
+		     exploded_node &src_enode,
+		     const superedge &sedge)
+  : m_eg (eg),
+    m_src_enode (src_enode),
+    m_sedge (sedge)
+  {
+  }
+
+  void DEBUG_FUNCTION dump () const;
+
+  logger *get_logger () const;
+
+  const extrinsic_state &get_ext_state () const;
+
+  const program_point &
+  get_initial_point () const;
+
+  const program_state &
+  get_initial_state () const;
+
+  const supergraph &
+  get_supergraph () const;
+
+  program_point
+  get_next_intraprocedural_point () const;
+
+  void
+  add_outcome (const program_point &dst_point,
+	       program_state dst_state,
+	       bool could_do_work,
+	       uncertainty_t *uncertainty,
+	       std::unique_ptr<custom_edge_info> info = nullptr);
+
+  exploded_graph &m_eg;
+  exploded_node &m_src_enode;
+  const superedge &m_sedge;
+};
+
+/* Abstract base class for an operation along a superedge.  */
+
+class operation
+{
+ public:
+  // Discriminator for concrete subclasses
+  enum kind
+  {
+    asm_stmt,
+    assignment,
+    predict_stmt,
+    return_stmt,
+    resx,
+    cond_edge,
+    goto_edge,
+    switch_edge,
+    eh_dispatch_try_edge,
+    eh_dispatch_allowed_edge,
+    phis,
+    call_and_return
+  };
+
+  ~operation () {}
+
+  void
+  dump () const;
+
+  virtual std::unique_ptr<operation>
+  clone () const = 0;
+
+  virtual void
+  print_as_edge_label (pretty_printer *pp, bool user_facing) const = 0;
+
+  virtual bool
+  defines_ssa_name_p (const_tree ssa_name) const = 0;
+
+  virtual void
+  walk_load_store_addr_ops (void *,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn) const = 0;
+
+  virtual const gimple *
+  maybe_get_stmt () const
+  {
+    return nullptr;
+  }
+
+  virtual void
+  execute (operation_context &op_ctxt) const = 0;
+
+  virtual bool
+  execute_for_feasibility (const exploded_edge &,
+			   feasibility_state &,
+			   region_model_context *,
+			   std::unique_ptr<rejected_constraint> */*out_rc*/) const
+  {
+    // no-op
+    return true;
+  }
+
+  /* Is this op suitable for bulk-merging?
+     It must have a single outcome, at the intraprocedural
+     next point, with some state.  */
+  virtual bool
+  supports_bulk_merge_p () const = 0;
+  virtual void
+  update_state_for_bulk_merger (const program_state &,
+				program_state &) const
+  {
+    /* Must be implemented for any subclasses that return true
+       for supports_bulk_merge_p.  */
+    gcc_unreachable ();
+  }
+  virtual void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const = 0;
+
+  virtual const control_flow_op *
+  dyn_cast_control_flow_op () const { return nullptr; }
+
+  virtual const call_and_return_op *
+  dyn_cast_call_and_return_op () const { return nullptr; }
+
+  virtual const phis_for_edge_op *
+  dyn_cast_phis_for_edge_op () const { return nullptr; }
+
+  enum kind get_kind () const { return m_kind; }
+
+protected:
+  operation (enum kind kind_)
+  : m_kind (kind_)
+  {
+  }
+
+  static void
+  handle_on_stmt_for_state_machines (operation_context &op_ctxt,
+				     program_state &dst_state,
+				     path_context *path_ctxt,
+				     bool &unknown_side_effects,
+				     const gimple &stmt);
+
+private:
+  enum kind m_kind;
+};
+
+/* Subclass for an operation representing a specific gimple stmt
+   that isn't control flow.  */
+
+class gimple_stmt_op : public operation
+{
+public:
+  const gimple &get_stmt () const { return m_stmt; }
+
+  void
+  print_as_edge_label (pretty_printer *pp, bool user_facing) const override;
+
+  bool
+  defines_ssa_name_p (const_tree ssa_name) const final override;
+
+  void
+  walk_load_store_addr_ops (void *,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn) const final override;
+
+  const gimple *
+  maybe_get_stmt () const final override
+  {
+    return &m_stmt;
+  }
+
+  void
+  execute (operation_context &op_ctxt) const override;
+
+  void
+  execute_on_state (operation_context &op_ctxt,
+		    program_state dst_state) const;
+
+  bool
+  execute_for_feasibility (const exploded_edge &,
+			   feasibility_state &,
+			   region_model_context *,
+			   std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+  virtual bool
+  supports_bulk_merge_p () const override;
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const override;
+
+protected:
+  gimple_stmt_op (enum kind kind_, const gimple &stmt)
+  : operation (kind_), m_stmt (stmt)
+  {}
+
+private:
+  const gimple &m_stmt;
+};
+
+/* Various subclasses of gimple_stmt_op.  */
+
+/* An operation subclass representing the effect of a GIMPLE_ASM stmt.  */
+
+class gasm_op : public gimple_stmt_op
+{
+public:
+  gasm_op (const gasm &asm_stmt)
+  : gimple_stmt_op (kind::asm_stmt, asm_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<gasm_op> (get_gasm ());
+  }
+
+  const gasm &get_gasm () const
+  {
+    return *as_a <const gasm *> (&get_stmt ());
+  }
+};
+
+/* An operation subclass representing the effect of a GIMPLE_ASSIGN stmt.  */
+
+class gassign_op : public gimple_stmt_op
+{
+public:
+  gassign_op (const gassign &assign_stmt)
+  : gimple_stmt_op (kind::assignment, assign_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<gassign_op> (get_gassign ());
+  }
+
+  const gassign &get_gassign () const
+  {
+    return *as_a <const gassign *> (&get_stmt ());
+  }
+};
+
+/* An operation subclass for a GIMPLE_PREDICT stmt.
+   They have no effect on state, but can be useful for reconstructing
+   where "return" statements were in the code the user originally wrote,
+   to improve the reported locations in diagnostics.  */
+
+class predict_op : public gimple_stmt_op
+{
+public:
+  predict_op (const gimple &predict_stmt)
+  : gimple_stmt_op (kind::predict_stmt, predict_stmt)
+  {
+    gcc_assert (predict_stmt.code == GIMPLE_PREDICT);
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<predict_op> (get_stmt ());
+  }
+};
+
+/* An operation subclass representing both:
+   (a) the effect of a GIMPLE_RETURN stmt: copying a value into the
+   RESULT_DECL of the current frame, and
+   (b) a hint when reporting diagnostics that this is the return
+   path from the function (rather than say, throwing an exception).  */
+
+class greturn_op : public gimple_stmt_op
+{
+public:
+  greturn_op (const greturn &return_stmt)
+  : gimple_stmt_op (kind::return_stmt, return_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<greturn_op> (get_greturn ());
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+  bool
+  execute_for_feasibility (const exploded_edge &,
+			   feasibility_state &,
+			   region_model_context *ctxt,
+			   std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+  bool
+  supports_bulk_merge_p () const final override
+  {
+    return false;
+  }
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const final override;
+
+  const greturn &get_greturn () const
+  {
+    return *as_a <const greturn *> (&get_stmt ());
+  }
+
+  tree get_retval () const
+  {
+    return gimple_return_retval (&get_greturn ());
+  }
+};
+
+/* A concrete operation subclass representing the effect of a GIMPLE_CALL stmt.
+
+   If the function is identified and has a known body, either simulate
+   it interprocedurally by pushing a stack frame and transitioning to the
+   callee, or simulate it intraprocedurally by replaying a summary of the
+   effects of the call.
+
+   If the function is identified but has an unknown body,
+   simulate it intraprocedurally, either using a known_function
+   subclass for precision, or following conservative rules that
+   assume various side-effects.
+
+   If the function is unidentified (for some kinds of dynamic calls),
+   simulate it intraprocedurally, following conservative rules that
+   assume various side-effects.
+
+   In the various intraprocedural simulation cases, the exploded edge will
+   correspond to the underlying superedge.
+
+   In the interprocedural simulation case, the exploded edge will
+   link two supernodes in different functions, and thus will require
+   custom edge info.
+
+   Various subclasses exist for handling awkward special cases,
+   such as longjmp.  */
+
+class call_and_return_op : public gimple_stmt_op
+{
+public:
+  static std::unique_ptr<operation>
+  make (const gcall &call_stmt);
+
+  std::unique_ptr<operation>
+  clone () const override
+  {
+    return std::make_unique<call_and_return_op> (get_gcall ());
+  }
+
+  const gcall &get_gcall () const
+  {
+    return *as_a <const gcall *> (&get_stmt ());
+  }
+
+  void
+  execute (operation_context &op_ctxt) const override;
+
+  bool
+  supports_bulk_merge_p () const final override
+  {
+    return false;
+  }
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const override;
+
+  const call_and_return_op *
+  dyn_cast_call_and_return_op () const final override { return this; }
+
+  tree
+  map_expr_from_caller_to_callee (tree callee_fndecl,
+				  tree caller_expr,
+				  callsite_expr *out) const;
+  tree
+  map_expr_from_callee_to_caller (tree callee_fndecl,
+				  tree callee_expr,
+				  callsite_expr *out) const;
+
+  call_and_return_op (const gcall &call_stmt)
+  : gimple_stmt_op (kind::call_and_return, call_stmt)
+  {
+  }
+
+  const known_function *
+  maybe_get_known_function (const call_details &cd) const;
+
+private:
+  cgraph_edge *
+  get_any_cgraph_edge (operation_context &op_ctxt) const;
+
+  void
+  replay_call_summaries (operation_context &op_ctxt,
+			 function &called_fn,
+			 per_function_data &called_fn_data,
+			 region_model_context *ctxt) const;
+
+  void
+  replay_call_summary (operation_context &op_ctxt,
+		       function &called_fn,
+		       call_summary &summary,
+		       region_model_context *ctxt) const;
+
+  tree
+  get_arg_for_parm (tree callee_fndecl,
+		    tree parm,
+		    callsite_expr *out) const;
+  tree
+  get_parm_for_arg (tree callee_fndecl,
+		    tree arg,
+		    callsite_expr *out) const;
+};
+
+/* A call to one of the various __analyzer_dump* functions.
+   These have no effect on state.  */
+
+class dump_op : public call_and_return_op
+{
+public:
+  enum dump_kind
+  {
+   state,
+   sarif,
+   dot,
+   state_2
+  };
+
+  dump_op (const gcall &call_stmt, enum dump_kind dump_kind_)
+  : call_and_return_op (call_stmt),
+    m_dump_kind (dump_kind_)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<dump_op> (get_gcall (), m_dump_kind);
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+private:
+  enum dump_kind m_dump_kind;
+};
+
+class setjmp_op : public call_and_return_op
+{
+public:
+  setjmp_op (const gcall &call_stmt)
+  : call_and_return_op (call_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<setjmp_op> (get_gcall ());
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const final override;
+};
+
+class longjmp_op : public call_and_return_op
+{
+public:
+  longjmp_op (const gcall &call_stmt)
+  : call_and_return_op (call_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<longjmp_op> (get_gcall ());
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+};
+
+class cxa_throw_op : public call_and_return_op
+{
+public:
+  cxa_throw_op (const gcall &call_stmt, bool is_rethrow)
+  : call_and_return_op (call_stmt),
+    m_is_rethrow (is_rethrow)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<cxa_throw_op> (get_gcall (), m_is_rethrow);
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+private:
+  bool m_is_rethrow;
+};
+
+class resx_op : public gimple_stmt_op
+{
+public:
+  resx_op (const gresx &resx_stmt)
+  : gimple_stmt_op (kind::resx, resx_stmt)
+  {
+  }
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<resx_op> (get_gresx ());
+  }
+
+  const gresx &get_gresx () const
+  {
+    return *as_a <const gresx *> (&get_stmt ());
+  }
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+  bool
+  supports_bulk_merge_p () const final override
+  {
+    return false;
+  }
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const final override;
+};
+
+/* An abstract subclass of operation representing the filtering effect on
+   state of a gimple control-flow statement at the end of a BB, for
+   a specific CFG out-edge from that BB.  */
+
+class control_flow_op : public operation
+{
+public:
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const override;
+
+  bool
+  defines_ssa_name_p (const_tree) const final override
+  {
+    return false;
+  }
+
+  void
+  walk_load_store_addr_ops (void *,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn) const final override;
+
+  const gimple *
+  maybe_get_stmt () const final override
+  {
+    return &m_ctrlflow_stmt;
+  }
+
+  virtual label_text
+  maybe_describe_condition (bool can_colorize) const;
+
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+  bool
+  supports_bulk_merge_p () const final override
+  {
+    return false;
+  }
+
+  bool
+  execute_for_feasibility (const exploded_edge &,
+			   feasibility_state &,
+			   region_model_context *,
+			   std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+  const control_flow_op *
+  dyn_cast_control_flow_op () const { return this; }
+
+  ::edge get_cfg_edge () const { return m_cfg_edge; }
+  int get_flags () const { return m_cfg_edge->flags; }
+  int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
+
+  const gimple &get_ctrlflow_stmt () const { return m_ctrlflow_stmt; }
+
+protected:
+  control_flow_op (enum kind kind_,
+		   ::edge cfg_edge,
+		   const gimple &ctrlflow_stmt)
+  : operation (kind_),
+    m_cfg_edge (cfg_edge),
+    m_ctrlflow_stmt (ctrlflow_stmt)
+  {}
+
+private:
+  virtual bool
+  apply_constraints (const superedge *sedge,
+		     region_model &model,
+		     region_model_context *ctxt,
+		     std::unique_ptr<rejected_constraint> *out) const = 0;
+
+  ::edge m_cfg_edge;
+  const gimple &m_ctrlflow_stmt;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+   transitions on a specific CFG edge after a GIMPLE_COND stmt, either the
+   "if (cond) is true" or the "if (cond) is false" branch.  */
+
+class gcond_edge_op : public control_flow_op
+{
+public:
+  gcond_edge_op (::edge cfg_edge,
+		 const gcond &cond_stmt);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<gcond_edge_op> (get_cfg_edge (),
+					    get_gcond ());
+  }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  label_text
+  maybe_describe_condition (bool can_colorize) const final override;
+
+  const gcond &get_gcond () const
+  {
+    return *as_a <const gcond *> (&get_ctrlflow_stmt ());
+  }
+
+private:
+  static label_text
+  maybe_describe_condition (bool can_colorize,
+			    tree lhs,
+			    enum tree_code op,
+			    tree rhs);
+  static bool should_print_expr_p (tree expr);
+
+  bool
+  apply_constraints (const superedge *sedge,
+		     region_model &model,
+		     region_model_context *ctxt,
+		     std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  bool m_true_value;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+   transitions on a specific CFG edge after a GIMPLE_GOTO stmt, thus
+   handling computed gotos.  */
+
+class ggoto_edge_op : public control_flow_op
+{
+public:
+  ggoto_edge_op (::edge cfg_edge,
+		 const ggoto &goto_stmt,
+		 tree dst_label);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<ggoto_edge_op> (get_cfg_edge (),
+					    get_ggoto (),
+					    m_dst_label);
+  }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  label_text
+  maybe_describe_condition (bool can_colorize) const final override;
+
+  const ggoto &get_ggoto () const
+  {
+    return *as_a <const ggoto *> (&get_ctrlflow_stmt ());
+  }
+
+private:
+  bool
+  apply_constraints (const superedge *sedge,
+		     region_model &model,
+		     region_model_context *ctxt,
+		     std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  tree m_dst_label;
+};
+
+/* Concrete operation subclass representing filtering/applying state
+   transitions on a specific CFG edge after a GIMPLE_SWITCH stmt, thus
+   handling a cluster of cases/default value.  */
+
+class switch_case_op : public control_flow_op
+{
+ public:
+  switch_case_op (function &fun,
+		  ::edge cfg_edge,
+		  const gswitch &switch_stmt,
+		  bounded_ranges_manager &mgr);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<switch_case_op> (*this);
+  }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  bool implicitly_created_default_p () const;
+
+  const gswitch &get_gswitch () const
+  {
+    return *as_a <const gswitch *> (&get_ctrlflow_stmt ());
+  }
+
+ private:
+  bool
+  apply_constraints (const superedge *sedge,
+		     region_model &model,
+		     region_model_context *ctxt,
+		     std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  std::vector<tree> m_case_labels;
+  const bounded_ranges *m_all_cases_ranges;
+};
+
+/* Abstract subclass for edges from eh_dispatch statements.  */
+
+class eh_dispatch_edge_op : public control_flow_op
+{
+public:
+  static std::unique_ptr<eh_dispatch_edge_op>
+  make (supernode *src,
+	supernode *dest,
+	::edge cfg_edge,
+	const geh_dispatch &geh_dispatch_stmt);
+
+  const geh_dispatch &
+  get_geh_dispatch () const
+  {
+    return *as_a <const geh_dispatch *> (&get_ctrlflow_stmt ());
+  }
+
+  eh_region
+  get_eh_region () const { return m_eh_region; }
+
+protected:
+  eh_dispatch_edge_op (supernode *src_snode,
+		       supernode *dst_snode,
+		       enum kind kind_,
+		       ::edge cfg_edge,
+		       const geh_dispatch &geh_dispatch_stmt,
+		       eh_region eh_reg);
+
+  supernode *get_src_snode () const { return m_src_snode; }
+
+private:
+  bool
+  apply_constraints (const superedge *sedge,
+		     region_model &model,
+		     region_model_context *ctxt,
+		     std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  virtual bool
+  apply_eh_constraints (const superedge *sedge,
+			region_model &model,
+			region_model_context *ctxt,
+			tree exception_type,
+			std::unique_ptr<rejected_constraint> *out) const = 0;
+
+  supernode *m_src_snode;
+  supernode *m_dst_snode;
+  eh_region m_eh_region;
+};
+
+/* Concrete operation for edges from an eh_dispatch statement
+   for ERT_TRY regions.  */
+
+class eh_dispatch_try_edge_op : public eh_dispatch_edge_op
+{
+public:
+  eh_dispatch_try_edge_op (supernode *src_snode,
+			   supernode *dst_snode,
+			   ::edge cfg_edge,
+			   const geh_dispatch &geh_dispatch_stmt,
+			   eh_region eh_reg,
+			   eh_catch ehc);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<eh_dispatch_try_edge_op> (*this);
+  }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const final override;
+
+private:
+  bool
+  apply_eh_constraints (const superedge *sedge,
+			region_model &model,
+			region_model_context *ctxt,
+			tree exception_type,
+			std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  eh_catch m_eh_catch;
+};
+
+/* Concrete operation for edges from an eh_dispatch statement
+   for ERT_ALLOWED_EXCEPTIONS regions.  */
+
+class eh_dispatch_allowed_edge_op : public eh_dispatch_edge_op
+{
+public:
+  enum eh_kind
+  {
+    expected,
+    unexpected
+  };
+
+  eh_dispatch_allowed_edge_op (supernode *src_snode,
+			       supernode *dst_snode,
+			       ::edge cfg_edge,
+			       const geh_dispatch &geh_dispatch_stmt,
+			       eh_region eh_reg);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<eh_dispatch_allowed_edge_op> (*this);
+  }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  enum eh_kind get_eh_kind () const { return m_kind; }
+
+private:
+  bool
+  apply_eh_constraints (const superedge *sedge,
+			region_model &model,
+			region_model_context *ctxt,
+			tree exception_type,
+			std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  enum eh_kind m_kind;
+};
+
+/* Concrete operation subclass representing the state transition
+   for simultaneously handling all of the phi nodes at the entry to a BB,
+   after following a specific CFG in-edge.
+   Note that this covers multiple gimple stmts: all of the gphi stmts
+   at a basic block entry (albeit for just one in-edge).
+   This can be thought of as handling one column of the entries in the
+   phi nodes of a BB (for a specific in-edge).
+   We ignore MEM entries, and discard phi nodes purely affecting them.  */
+
+class phis_for_edge_op : public operation
+{
+public:
+  /* A "dst=src;" pair within a phi node.  */
+  struct pair
+  {
+    tree m_dst;
+    tree m_src;
+  };
+
+  static std::unique_ptr<operation>
+  maybe_make (::edge cfg_in_edge);
+
+  std::unique_ptr<operation>
+  clone () const final override
+  {
+    return std::make_unique<phis_for_edge_op> (*this);
+  }
+
+  phis_for_edge_op (std::vector<pair> &&pairs,
+		    ::edge cfg_in_edge);
+
+  const phis_for_edge_op *
+  dyn_cast_phis_for_edge_op () const final override { return this; }
+
+  void
+  print_as_edge_label (pretty_printer *pp,
+		       bool user_facing) const final override;
+
+  bool
+  defines_ssa_name_p (const_tree ssa_name) const final override;
+
+  void
+  walk_load_store_addr_ops (void *,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn,
+			    walk_stmt_load_store_addr_fn) const final override;
+  void
+  execute (operation_context &op_ctxt) const final override;
+
+  bool
+  execute_for_feasibility (const exploded_edge &,
+			   feasibility_state &,
+			   region_model_context *,
+			   std::unique_ptr<rejected_constraint> *out_rc) const override;
+
+  bool
+  supports_bulk_merge_p () const final override
+  {
+    return true;
+  }
+  void
+  update_state_for_bulk_merger (const program_state &src_state,
+				program_state &dst_state) const final override;
+
+  void
+  add_any_events_for_eedge (const exploded_edge &eedge,
+			    checker_path &out_path) const final override;
+
+  const std::vector<pair> &get_pairs () const { return m_pairs; }
+
+private:
+  static std::vector<pair>
+  get_pairs_for_phi_along_in_edge (::edge cfg_in_edge);
+
+  void
+  update_state (const program_state &src_state,
+		program_state &dst_state,
+		region_model_context *ctxt) const;
+
+  std::vector<pair> m_pairs;
+  ::edge m_cfg_in_edge;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_OPS_H */
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index 14d8f9f803fd..e98303263909 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<call_event> (eedge,
-				   event_loc_info (last_stmt
-						   ? last_stmt->location
-						   : UNKNOWN_LOCATION,
-						   src_point.get_fndecl (),
-						   src_stack_depth)));
+				   event_loc_info (eedge.m_src)));
 }
 
 /* Base implementation of pending_diagnostic::add_region_creation_events.
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index b5d90a22ae1f..414497955609 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 9baa0074b2b9..15129f5c989f 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -45,195 +45,6 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
-/* Get a string for PK.  */
-
-const char *
-point_kind_to_string (enum point_kind pk)
-{
-  switch (pk)
-    {
-    default:
-      gcc_unreachable ();
-    case PK_ORIGIN:
-      return "PK_ORIGIN";
-    case PK_BEFORE_SUPERNODE:
-      return "PK_BEFORE_SUPERNODE";
-    case PK_BEFORE_STMT:
-      return "PK_BEFORE_STMT";
-    case PK_AFTER_SUPERNODE:
-      return "PK_AFTER_SUPERNODE";
-    case PK_EMPTY:
-      return "PK_EMPTY";
-    case PK_DELETED:
-      return "PK_DELETED";
-    }
-}
-
-/* class function_point.  */
-
-function_point::function_point (const supernode *supernode,
-				const superedge *from_edge,
-				unsigned stmt_idx,
-				enum point_kind kind)
-: m_supernode (supernode), m_from_edge (from_edge),
-  m_stmt_idx (stmt_idx), m_kind (kind)
-{
-  if (from_edge)
-    {
-      gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
-      gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
-    }
-  if (stmt_idx)
-    gcc_checking_assert (m_kind == PK_BEFORE_STMT);
-}
-
-/* Print this function_point to PP.  */
-
-void
-function_point::print (pretty_printer *pp, const format &f) const
-{
-  switch (get_kind ())
-    {
-    default:
-      gcc_unreachable ();
-
-    case PK_ORIGIN:
-      pp_printf (pp, "origin");
-      if (f.m_newlines)
-	pp_newline (pp);
-      break;
-
-    case PK_BEFORE_SUPERNODE:
-      {
-	if (m_from_edge)
-	  {
-	    if (basic_block bb = m_from_edge->m_src->m_bb)
-	      pp_printf (pp, "before SN: %i (from SN: %i (bb: %i))",
-			 m_supernode->m_index, m_from_edge->m_src->m_index,
-			 bb->index);
-	    else
-	      pp_printf (pp, "before SN: %i (from SN: %i)",
-			 m_supernode->m_index, m_from_edge->m_src->m_index);
-	  }
-	else
-	  pp_printf (pp, "before SN: %i (NULL from-edge)",
-		     m_supernode->m_index);
-	f.spacer (pp);
-	for (gphi_iterator gpi
-	       = const_cast<supernode *>(get_supernode ())->start_phis ();
-	     !gsi_end_p (gpi); gsi_next (&gpi))
-	  {
-	    const gphi *phi = gpi.phi ();
-	    pp_gimple_stmt_1 (pp, phi, 0, (dump_flags_t)0);
-	  }
-      }
-      break;
-
-    case PK_BEFORE_STMT:
-      pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
-		 m_stmt_idx);
-      f.spacer (pp);
-      pp_gimple_stmt_1 (pp, get_stmt (), 0, (dump_flags_t)0);
-      if (f.m_newlines)
-	{
-	  pp_newline (pp);
-	  print_source_line (pp);
-	}
-      break;
-
-    case PK_AFTER_SUPERNODE:
-      pp_printf (pp, "after SN: %i", m_supernode->m_index);
-      if (f.m_newlines)
-	pp_newline (pp);
-      break;
-    }
-}
-
-/* Generate a hash value for this function_point.  */
-
-hashval_t
-function_point::hash () const
-{
-  inchash::hash hstate;
-  if (m_supernode)
-    hstate.add_int (m_supernode->m_index);
-  hstate.add_ptr (m_from_edge);
-  hstate.add_int (m_stmt_idx);
-  hstate.add_int (m_kind);
-  return hstate.end ();
-}
-
-/* Get the function at this point, if any.  */
-
-function *
-function_point::get_function () const
-{
-  if (m_supernode)
-    return m_supernode->m_fun;
-  else
-    return nullptr;
-}
-
-/* Get the gimple stmt for this function_point, if any.  */
-
-const gimple *
-function_point::get_stmt () const
-{
-  if (m_kind == PK_BEFORE_STMT)
-    return m_supernode->m_stmts[m_stmt_idx];
-  else if (m_kind == PK_AFTER_SUPERNODE)
-    return m_supernode->get_last_stmt ();
-  else
-    return nullptr;
-}
-
-/* Get a location for this function_point, if any.  */
-
-location_t
-function_point::get_location () const
-{
-  const gimple *stmt = get_stmt ();
-  if (stmt)
-    return stmt->location;
-  if (m_kind == PK_BEFORE_SUPERNODE)
-    return m_supernode->get_start_location ();
-  else if (m_kind == PK_AFTER_SUPERNODE)
-    return m_supernode->get_end_location ();
-  else
-    return UNKNOWN_LOCATION;
-}
-
-/* Return true if this function_point is a before_stmt for
-   the final stmt in its supernode.  */
-
-bool
-function_point::final_stmt_p () const
-{
-  if (m_kind != PK_BEFORE_STMT)
-    return false;
-  return m_stmt_idx == get_supernode ()->m_stmts.length () - 1;
-}
-
-/* Create a function_point representing the entrypoint of function FUN.  */
-
-function_point
-function_point::from_function_entry (const supergraph &sg, const function &fun)
-{
-  return before_supernode (sg.get_node_for_function_entry (fun), nullptr);
-}
-
-/* Create a function_point representing entering supernode SUPERNODE,
-   having reached it via FROM_EDGE (which could be nullptr).  */
-
-function_point
-function_point::before_supernode (const supernode *supernode,
-				  const superedge *from_edge)
-{
-  if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
-    from_edge = nullptr;
-  return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
-}
-
 /* A subclass of diagnostics::context for use by
    program_point::print_source_line.  */
 
@@ -253,25 +64,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 +90,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 +113,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<json::object>
@@ -305,39 +121,13 @@ program_point::to_json () const
 {
   auto point_obj = std::make_unique<json::object> ();
 
-  point_obj->set_string ("kind", point_kind_to_string (get_kind ()));
-
   if (get_supernode ())
-    point_obj->set_integer ("snode_idx", get_supernode ()->m_index);
-
-  switch (get_kind ())
-    {
-    default: break;
-    case PK_BEFORE_SUPERNODE:
-      if (const superedge *sedge = get_from_edge ())
-	point_obj->set_integer ("from_edge_snode_idx", sedge->m_src->m_index);
-      break;
-    case PK_BEFORE_STMT:
-      point_obj->set_integer ("stmt_idx", get_stmt_idx ());
-      break;
-    }
-
+    point_obj->set_integer ("snode_idx", get_supernode ()->m_id);
   point_obj->set ("call_string", m_call_string->to_json ());
 
   return point_obj;
 }
 
-/* Update the callstack to represent a call from caller to callee.
-
-   Generally used to push a custom call to a perticular program point
-   where we don't have a superedge representing the call.  */
-void
-program_point::push_to_call_stack (const supernode *caller,
-				   const supernode *callee)
-{
-  m_call_string = m_call_string->push_call (callee, caller);
-}
-
 /* Pop the topmost call from the current callstack.  */
 void
 program_point::pop_from_call_stack ()
@@ -352,7 +142,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 +154,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 +178,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 <const cfg_superedge *> (succ);
-
-	if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
-	  {
-	    const supernode *src_snode = cfg_sedge->m_src;
-	    if (gimple *last_stmt = src_snode->get_last_stmt ())
-	      if (last_stmt->code == GIMPLE_GOTO)
-		{
-		  /* For the program_point aspect here, consider all
-		     out-edges from goto stmts to be valid; we'll
-		     consider state separately.  */
-		  return true;
-		}
-
-	    /* Reject other kinds of abnormal edges;
-	       we special-case setjmp/longjmp.  */
-	    return false;
-	  }
-      }
-      break;
-
-    case SUPEREDGE_CALL:
-      {
-	const call_superedge *call_sedge = as_a <const call_superedge *> (succ);
-
-	if (eg.get_analysis_plan ().use_summary_p (call_sedge->m_cedge))
-	  {
-	    if (logger)
-	      logger->log ("rejecting call edge: using summary instead");
-	    return false;
-	  }
-
-	/* Add the callsite to the call string.  */
-	m_call_string = m_call_string->push_call (eg.get_supergraph (),
-						  call_sedge);
-
-	/* Impose a maximum recursion depth and don't analyze paths
-	   that exceed it further.
-	   This is something of a blunt workaround, but it only
-	   applies to recursion (and mutual recursion), not to
-	   general call stacks.  */
-	if (m_call_string->calc_recursion_depth ()
-	    > param_analyzer_max_recursion_depth)
-	  {
-	    if (logger)
-	      logger->log ("rejecting call edge: recursion limit exceeded");
-	    // TODO: issue a sorry for this?
-	    return false;
-	  }
-      }
-      break;
-
-    case SUPEREDGE_RETURN:
-      {
-	/* Require that we return to the call site in the call string.  */
-	if (m_call_string->empty_p ())
-	  {
-	    if (logger)
-	      logger->log ("rejecting return edge: empty call string");
-	    return false;
-	  }
-	const call_string::element_t &top_of_stack
-	  = m_call_string->get_top_of_stack ();
-	m_call_string = m_call_string->get_parent ();
-	call_string::element_t current_call_string_element (succ->m_dest,
-							    succ->m_src);
-	if (top_of_stack != current_call_string_element)
-	  {
-	    if (logger)
-	      logger->log ("rejecting return edge: return to wrong callsite");
-	    return false;
-	  }
-      }
-      break;
-
-    case SUPEREDGE_INTRAPROCEDURAL_CALL:
-      {
-	const callgraph_superedge *cg_sedge
-	  = as_a <const callgraph_superedge *> (succ);
-	/* Consider turning this edge into a use of an
-	   interprocedural summary.  */
-	if (eg.get_analysis_plan ().use_summary_p (cg_sedge->m_cedge))
-	  {
-	    if (logger)
-	      logger->log ("using function summary for %qE in %qE",
-			   cg_sedge->get_callee_decl (),
-			   cg_sedge->get_caller_decl ());
-	    return true;
-	  }
-	else
-	  {
-	    /* Otherwise, we ignore these edges  */
-	    if (logger)
-	      logger->log ("rejecting interprocedural edge");
-	    return false;
-	  }
-      }
-    }
-
-  return true;
-}
-
-/* Comparator for program points within the same supernode,
-   for implementing worklist::key_t comparison operators.
-   Return negative if POINT_A is before POINT_B
-   Return positive if POINT_A is after POINT_B
-   Return 0 if they are equal.  */
-
-int
-function_point::cmp_within_supernode_1 (const function_point &point_a,
-					const function_point &point_b)
-{
-  gcc_assert (point_a.get_supernode () == point_b.get_supernode ());
-
-  switch (point_a.m_kind)
-    {
-    default:
-      gcc_unreachable ();
-    case PK_BEFORE_SUPERNODE:
-      switch (point_b.m_kind)
-	{
-	default:
-	  gcc_unreachable ();
-	case PK_BEFORE_SUPERNODE:
-	  {
-	    int a_src_idx = -1;
-	    int b_src_idx = -1;
-	    if (point_a.m_from_edge)
-	      a_src_idx = point_a.m_from_edge->m_src->m_index;
-	    if (point_b.m_from_edge)
-	      b_src_idx = point_b.m_from_edge->m_src->m_index;
-	    return a_src_idx - b_src_idx;
-	  }
-	  break;
-
-	case PK_BEFORE_STMT:
-	case PK_AFTER_SUPERNODE:
-	  return -1;
-	}
-      break;
-    case PK_BEFORE_STMT:
-      switch (point_b.m_kind)
-	{
-	default:
-	  gcc_unreachable ();
-	case PK_BEFORE_SUPERNODE:
-	  return 1;
-
-	case PK_BEFORE_STMT:
-	  return point_a.m_stmt_idx - point_b.m_stmt_idx;
-
-	case PK_AFTER_SUPERNODE:
-	  return -1;
-	}
-      break;
-    case PK_AFTER_SUPERNODE:
-      switch (point_b.m_kind)
-	{
-	default:
-	  gcc_unreachable ();
-	case PK_BEFORE_SUPERNODE:
-	case PK_BEFORE_STMT:
-	  return 1;
-
-	case PK_AFTER_SUPERNODE:
-	  return 0;
-	}
-      break;
-    }
-}
-
-/* Comparator for program points within the same supernode,
-   for implementing worklist::key_t comparison operators.
-   Return negative if POINT_A is before POINT_B
-   Return positive if POINT_A is after POINT_B
-   Return 0 if they are equal.  */
-
-int
-function_point::cmp_within_supernode (const function_point &point_a,
-				      const function_point &point_b)
-{
-  int result = cmp_within_supernode_1 (point_a, point_b);
-
-  /* Check that the ordering is symmetric  */
-#if CHECKING_P
-  int reversed = cmp_within_supernode_1 (point_b, point_a);
-  gcc_assert (reversed == -result);
-#endif
-
-  return result;
-}
-
-/* Comparator for imposing an order on function_points.  */
-
-int
-function_point::cmp (const function_point &point_a,
-		     const function_point &point_b)
-{
-  int idx_a = point_a.m_supernode ? point_a.m_supernode->m_index : -1;
-  int idx_b = point_b.m_supernode ? point_b.m_supernode->m_index : -1;
-  if (int cmp_idx = idx_a - idx_b)
-    return cmp_idx;
-  gcc_assert (point_a.m_supernode == point_b.m_supernode);
-  if (point_a.m_supernode)
-    return cmp_within_supernode (point_a, point_b);
-  else
-    return 0;
-}
-
-/* Comparator for use by vec<function_point>::qsort.  */
-
-int
-function_point::cmp_ptr (const void *p1, const void *p2)
-{
-  const function_point *fp1 = (const function_point *)p1;
-  const function_point *fp2 = (const function_point *)p2;
-  return cmp (*fp1, *fp2);
-}
-
-/* For PK_BEFORE_STMT, go to next stmt (or to PK_AFTER_SUPERNODE).  */
-
-void
-function_point::next_stmt ()
-{
-  gcc_assert (m_kind == PK_BEFORE_STMT);
-  if (++m_stmt_idx == m_supernode->m_stmts.length ())
-    {
-      m_kind = PK_AFTER_SUPERNODE;
-      m_stmt_idx = 0;
-    }
-}
-
-/* For those function points for which there is a uniquely-defined
-   successor, return it.  */
-
-function_point
-function_point::get_next () const
-{
-  switch (get_kind ())
-    {
-    default:
-      gcc_unreachable ();
-    case PK_ORIGIN:
-    case PK_AFTER_SUPERNODE:
-      gcc_unreachable (); /* Not uniquely defined.  */
-    case PK_BEFORE_SUPERNODE:
-      if (get_supernode ()->m_stmts.length () > 0)
-	return before_stmt (get_supernode (), 0);
-      else
-	return after_supernode (get_supernode ());
-    case PK_BEFORE_STMT:
-      {
-	unsigned next_idx = get_stmt_idx () + 1;
-	if (next_idx < get_supernode ()->m_stmts.length ())
-	  return before_stmt (get_supernode (), next_idx);
-	else
-	  return after_supernode (get_supernode ());
-      }
-    }
-}
-
 /* class program_point.  */
 
 program_point
 program_point::origin (const region_model_manager &mgr)
 {
-  return program_point (function_point (nullptr, nullptr,
-					0, PK_ORIGIN),
+  return program_point (nullptr,
 			mgr.get_empty_call_string ());
 }
 
@@ -684,39 +192,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 +241,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<function_point> points;
-  points.safe_push (function_point::before_supernode (snode, nullptr));
-  points.safe_push (function_point::before_stmt (snode, 0));
-  points.safe_push (function_point::before_stmt (snode, 1));
-  points.safe_push (function_point::after_supernode (snode));
-
-  /* Check all pairs.  */
-  unsigned i;
-  function_point *point_a;
-  FOR_EACH_VEC_ELT (points, i, point_a)
-    {
-      unsigned j;
-      function_point *point_b;
-      FOR_EACH_VEC_ELT (points, j, point_b)
-	{
-	  int cmp = function_point::cmp_within_supernode (*point_a, *point_b);
-	  if (i == j)
-	    ASSERT_EQ (cmp, 0);
-	  if (i < j)
-	    ASSERT_TRUE (cmp < 0);
-	  if (i > j)
-	    ASSERT_TRUE (cmp > 0);
-	}
-    }
-}
-
 /* Verify that program_point::operator== works as expected.  */
 
 static void
@@ -821,14 +252,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 +263,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 fc543c01a73c..cd365a128188 100644
--- a/gcc/analyzer/program-point.h
+++ b/gcc/analyzer/program-point.h
@@ -23,30 +23,10 @@ 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 {
 
-class exploded_graph;
-
-/* An enum for distinguishing the various kinds of program_point.  */
-
-enum point_kind {
-  /* A "fake" node which has edges to all entrypoints.  */
-  PK_ORIGIN,
-
-  PK_BEFORE_SUPERNODE,
-  PK_BEFORE_STMT,
-  PK_AFTER_SUPERNODE,
-
-  /* Special values used for hash_map:  */
-  PK_EMPTY,
-  PK_DELETED,
-
-  NUM_POINT_KINDS
-};
-
-extern const char *point_kind_to_string (enum point_kind pk);
-
 class format
 {
 public:
@@ -63,107 +43,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 +53,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<json::object> to_json () const;
 
   hashval_t hash () const;
   bool operator== (const program_point &other) const
   {
-    return (m_function_point == other.m_function_point
+    return (m_snode == other.m_snode
 	    && m_call_string == other.m_call_string);
   }
   bool operator!= (const program_point &other) const
@@ -199,42 +79,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 +106,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 7962b452f8b5..f803adbe5894 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<custom_edge_info>) final override
-    {
-      gcc_unreachable ();
-    }
-
-    void terminate_path () final override
-    {
-      m_terminated = true;
-    }
-
-    bool terminate_path_p () const final override
-    {
-      return m_terminated;
-    }
-    bool &m_terminated;
-  };
-
-  /* Update state.  */
-  const program_point &point = enode->get_point ();
-  const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
-  /* For conditionals and switch statements, add the
-     relevant conditions (for the specific edge) to new_state;
-     skip edges for which the resulting constraints
-     are impossible.
-     This also updates frame information for call/return superedges.
-     Adding the relevant conditions for the edge could also trigger
-     sm-state transitions (e.g. transitions due to ptrs becoming known
-     to be NULL or non-NULL) */
-  bool terminated = false;
-  my_path_context path_ctxt (terminated);
-  impl_region_model_context ctxt (eg, enode,
-				  &enode->get_state (),
-				  this,
-				  uncertainty, &path_ctxt,
-				  last_stmt);
-  std::unique_ptr<rejected_constraint> rc;
-  logger * const logger = eg.get_logger ();
-  if (!m_region_model->maybe_update_for_edge (*succ,
-					      last_stmt,
-					      &ctxt,
-					      logger ? &rc : nullptr))
-    {
-      if (logger)
-	{
-	  logger->start_log_line ();
-	  logger->log_partial ("edge to SN: %i is impossible"
-			       " due to region_model constraint: ",
-			       succ->m_dest->m_index);
-	  rc->dump_to_pp (logger->get_printer ());
-	  logger->end_log_line ();
-	}
-      return false;
-    }
-  if (terminated)
-    return false;
-
-  program_state::detect_leaks (enode->get_state (), *this,
-			       nullptr, eg.get_ext_state (),
-			       &ctxt);
-
-  return true;
-}
-
-/* Update this program_state to reflect a call to function
-   represented by CALL_STMT.
-   currently used only when the call doesn't have a superedge representing
-   the call ( like call via a function pointer )  */
-void
-program_state::push_call (exploded_graph &eg,
-                          exploded_node *enode,
-                          const gcall &call_stmt,
-                          uncertainty_t *uncertainty)
-{
-  /* Update state.  */
-  const program_point &point = enode->get_point ();
-  const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
-  impl_region_model_context ctxt (eg, enode,
-                                  &enode->get_state (),
-                                  this,
-                                  uncertainty,
-				  nullptr,
-                                  last_stmt);
-  m_region_model->update_for_gcall (call_stmt, &ctxt);
-}
-
-/* Update this program_state to reflect a return from function
-   call to which is represented by CALL_STMT.
-   currently used only when the call doesn't have a superedge representing
-   the return */
-void
-program_state::returning_call (exploded_graph &eg,
-                               exploded_node *enode,
-                               const gcall &call_stmt,
-                               uncertainty_t *uncertainty)
-{
-  /* Update state.  */
-  const program_point &point = enode->get_point ();
-  const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
-
-  impl_region_model_context ctxt (eg, enode,
-                                  &enode->get_state (),
-                                  this,
-                                  uncertainty,
-				  nullptr,
-                                  last_stmt);
-  m_region_model->update_for_return_gcall (call_stmt, &ctxt);
-}
-
 /* Generate a simpler version of THIS, discarding state that's no longer
    relevant at POINT.
    The idea is that we're more likely to be able to consolidate
@@ -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<std::unique_ptr<state_machine>> checkers;
   const state_machine &borrowed_sm = *sm.get ();
   checkers.push_back (std::move (sm));
-  engine eng;
+  region_model_manager mgr;
+  engine eng (mgr);
   extrinsic_state ext_state (std::move (checkers), &eng);
 
   /* Test setting states on svalue_id instances directly.  */
   {
     const state_machine::state test_state_42 ("test state 42", 42);
     const state_machine::state_t TEST_STATE_42 = &test_state_42;
-    region_model_manager mgr;
     region_model model (&mgr);
     const svalue *x_sval = model.get_rvalue (x, nullptr);
     const svalue *y_sval = model.get_rvalue (y, nullptr);
@@ -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<std::unique_ptr<state_machine>> checkers;
-  engine eng;
+  region_model_manager mgr;
+  engine eng (mgr);
   extrinsic_state ext_state (std::move (checkers), &eng);
 
   program_state s (ext_state);
@@ -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 4278237adc80..97e696d44379 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 872b1d6a8528..2a6d047f4ce7 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 865bedfb8624..a2c59c4157a1 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 ceb5064007cb..3ef5291d7b06 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<jump_through_null>
+{
+public:
+  jump_through_null (const gcall &call)
+  : m_call (call)
+  {}
+
+  const char *get_kind () const final override
+  {
+    return "jump_through_null";
+  }
+
+  bool operator== (const jump_through_null &other) const
+  {
+    return &m_call == &other.m_call;
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_jump_through_null;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    return ctxt.warn ("jump through null pointer");
+  }
+
+  bool describe_final_event (pretty_printer &pp,
+			     const evdesc::final_event &) final override
+  {
+    pp_string (&pp, "jump through null pointer here");
+    return true;
+  }
+
+private:
+  const gcall &m_call;
+};
 /* Update this model for the CALL stmt, using CTXT to report any
    diagnostics - the first half.
 
@@ -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<jump_through_null> (call));
+		ctxt->terminate_path ();
+		return true;
+	      }
+	  }
+
       check_for_throw_inside_call (call, NULL_TREE, ctxt);
       cd.set_any_lhs_with_defaults ();
       return true; /* Unknown side effects.  */
@@ -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<const svalue *> &svals_changing_meaning,
-			  region_model_context *ctxt)
-{
-  /* For now, don't bother tracking the .MEM SSA names.  */
-  if (tree var = SSA_NAME_VAR (lhs))
-    if (TREE_CODE (var) == VAR_DECL)
-      if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
-	return;
-
-  const svalue *src_sval = old_state.get_rvalue (rhs, ctxt);
-  const region *dst_reg = old_state.get_lvalue (lhs, ctxt);
-
-  const svalue *sval = old_state.get_rvalue (lhs, nullptr);
-  if (sval->get_kind () == SK_WIDENING)
-    svals_changing_meaning.add (sval);
-
-  set_value (dst_reg, src_sval, ctxt);
-
-  if (ctxt)
-    ctxt->on_phi (phi, rhs);
-}
-
 /* Implementation of region_model::get_lvalue; the latter adds type-checking.
 
    Get the id of the region for PV within this region_model,
@@ -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<const svalue *> svals_changing_meaning;
-
-  for (gphi_iterator gpi = const_cast<supernode *>(snode)->start_phis ();
-       !gsi_end_p (gpi); gsi_next (&gpi))
-    {
-      gphi *phi = gpi.phi ();
-
-      tree src = last_cfg_superedge->get_phi_arg (phi);
-      tree lhs = gimple_phi_result (phi);
-
-      /* Update next_state based on phi and old_state.  */
-      handle_phi (phi, lhs, src, old_state, svals_changing_meaning, ctxt);
-    }
-
-  for (auto iter : svals_changing_meaning)
-    m_constraints->purge_state_involving (iter);
-}
-
-/* Attempt to update this model for taking EDGE (where the last statement
-   was LAST_STMT), returning true if the edge can be taken, false
-   otherwise.
-   When returning false, if OUT is non-NULL, write a new rejected_constraint
-   to it.
-
-   For CFG superedges where LAST_STMT is a conditional or a switch
-   statement, attempt to add the relevant conditions for EDGE to this
-   model, returning true if they are feasible, or false if they are
-   impossible.
-
-   For call superedges, push frame information and store arguments
-   into parameters.
-
-   For return superedges, pop frame information and store return
-   values into any lhs.
-
-   Rejection of call/return superedges happens elsewhere, in
-   program_point::on_edge (i.e. based on program point, rather
-   than program state).  */
-
-bool
-region_model::maybe_update_for_edge (const superedge &edge,
-				     const gimple *last_stmt,
-				     region_model_context *ctxt,
-				     std::unique_ptr<rejected_constraint> *out)
-{
-  /* Handle frame updates for interprocedural edges.  */
-  switch (edge.m_kind)
-    {
-    default:
-      break;
-
-    case SUPEREDGE_CALL:
-      {
-	const call_superedge *call_edge = as_a <const call_superedge *> (&edge);
-	update_for_call_superedge (*call_edge, ctxt);
-      }
-      break;
-
-    case SUPEREDGE_RETURN:
-      {
-	const return_superedge *return_edge
-	  = as_a <const return_superedge *> (&edge);
-	update_for_return_superedge (*return_edge, ctxt);
-      }
-      break;
-
-    case SUPEREDGE_INTRAPROCEDURAL_CALL:
-      /* This is a no-op for call summaries; we should already
-	 have handled the effect of the call summary at the call stmt.  */
-      break;
-    }
-
-  if (last_stmt == nullptr)
-    return true;
-
-  /* Apply any constraints for conditionals/switch/computed-goto statements.  */
-
-  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
-    {
-      const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
-      return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt, out);
-    }
-
-  if (const gswitch *switch_stmt = dyn_cast <const gswitch *> (last_stmt))
-    {
-      const switch_cfg_superedge *switch_sedge
-	= as_a <const switch_cfg_superedge *> (&edge);
-      return apply_constraints_for_gswitch (*switch_sedge, switch_stmt,
-					    ctxt, out);
-    }
-
-  if (const geh_dispatch *eh_dispatch_stmt
-	= dyn_cast <const geh_dispatch *> (last_stmt))
-    {
-      const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge
-	= as_a <const eh_dispatch_cfg_superedge *> (&edge);
-      return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge,
-						eh_dispatch_stmt,
-						ctxt, out);
-    }
-
-  if (const ggoto *goto_stmt = dyn_cast <const ggoto *> (last_stmt))
-    {
-      const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
-      return apply_constraints_for_ggoto (*cfg_sedge, goto_stmt, ctxt);
-    }
-
-  return true;
-}
-
 /* Push a new frame_region on to the stack region.
    Populate the frame_region with child regions for the function call's
    parameters, using values from the arguments at the callsite in the
@@ -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<rejected_constraint> *out)
-{
-  ::edge cfg_edge = sedge.get_cfg_edge ();
-  gcc_assert (cfg_edge != nullptr);
-  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
-
-  enum tree_code op = gimple_cond_code (cond_stmt);
-  tree lhs = gimple_cond_lhs (cond_stmt);
-  tree rhs = gimple_cond_rhs (cond_stmt);
-  if (cfg_edge->flags & EDGE_FALSE_VALUE)
-    op = invert_tree_comparison (op, false /* honor_nans */);
-  return add_constraint (lhs, op, rhs, ctxt, out);
-}
-
-/* Return true iff SWITCH_STMT has a non-default label that contains
-   INT_CST.  */
-
-static bool
-has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst)
-{
-  /* We expect the initial label to be the default; skip it.  */
-  gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL_TREE);
-  unsigned min_idx = 1;
-  unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1;
-
-  /* Binary search: try to find the label containing INT_CST.
-     This requires the cases to be sorted by CASE_LOW (done by the
-     gimplifier).  */
-  while (max_idx >= min_idx)
-    {
-      unsigned case_idx = (min_idx + max_idx) / 2;
-      tree label =  gimple_switch_label (switch_stmt, case_idx);
-      tree low = CASE_LOW (label);
-      gcc_assert (low);
-      tree high = CASE_HIGH (label);
-      if (!high)
-	high = low;
-      if (tree_int_cst_compare (int_cst, low) < 0)
-	{
-	  /* INT_CST is below the range of this label.  */
-	  gcc_assert (case_idx > 0);
-	  max_idx = case_idx - 1;
-	}
-      else if (tree_int_cst_compare (int_cst, high) > 0)
-	{
-	  /* INT_CST is above the range of this case.  */
-	  min_idx = case_idx + 1;
-	}
-      else
-	/* This case contains INT_CST.  */
-	return true;
-    }
-  /* Not found.  */
-  return false;
-}
-
-/* Return true iff SWITCH_STMT (which must be on an enum value)
-   has nondefault cases handling all values in the enum.  */
-
-static bool
-has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt,
-					    tree type)
-{
-  gcc_assert (switch_stmt);
-  gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE);
-
-  for (tree enum_val_iter = TYPE_VALUES (type);
-       enum_val_iter;
-       enum_val_iter = TREE_CHAIN (enum_val_iter))
-    {
-      tree enum_val = TREE_VALUE (enum_val_iter);
-      gcc_assert (TREE_CODE (enum_val) == CONST_DECL);
-      gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST);
-      if (!has_nondefault_case_for_value_p (switch_stmt,
-					    DECL_INITIAL (enum_val)))
-	return false;
-    }
-  return true;
-}
-
-/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
-   for the edge to be taken.
-
-   If they are feasible, add the constraints and return true.
-
-   Return false if the constraints contradict existing knowledge
-   (and so the edge should not be taken).
-   When returning false, if OUT is non-NULL, write a new rejected_constraint
-   to it.  */
-
-bool
-region_model::
-apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
-			       const gswitch *switch_stmt,
-			       region_model_context *ctxt,
-			       std::unique_ptr<rejected_constraint> *out)
-{
-  tree index  = gimple_switch_index (switch_stmt);
-  const svalue *index_sval = get_rvalue (index, ctxt);
-  bool check_index_type = true;
-
-  /* With -fshort-enum, there may be a type cast.  */
-  if (ctxt && index_sval->get_kind () == SK_UNARYOP
-      && TREE_CODE (index_sval->get_type ()) == INTEGER_TYPE)
-    {
-      const unaryop_svalue *unaryop = as_a <const unaryop_svalue *> (index_sval);
-      if (unaryop->get_op () == NOP_EXPR
-	  && is_a <const initial_svalue *> (unaryop->get_arg ()))
-	if (const initial_svalue *initvalop = (as_a <const initial_svalue *>
-					       (unaryop->get_arg ())))
-	  if (initvalop->get_type ()
-	      && TREE_CODE (initvalop->get_type ()) == ENUMERAL_TYPE)
-	    {
-	      index_sval = initvalop;
-	      check_index_type = false;
-	    }
-    }
-
-  /* If we're switching based on an enum type, assume that the user is only
-     working with values from the enum.  Hence if this is an
-     implicitly-created "default", assume it doesn't get followed.
-     This fixes numerous "uninitialized" false positives where we otherwise
-     consider jumping past the initialization cases.  */
-
-  if (/* Don't check during feasibility-checking (when ctxt is NULL).  */
-      ctxt
-      /* Must be an enum value.  */
-      && index_sval->get_type ()
-      && (!check_index_type
-	  || TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE)
-      && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE
-      /* If we have a constant, then we can check it directly.  */
-      && index_sval->get_kind () != SK_CONSTANT
-      && edge.implicitly_created_default_p ()
-      && has_nondefault_cases_for_all_enum_values_p (switch_stmt,
-						     index_sval->get_type ())
-      /* Don't do this if there's a chance that the index is
-	 attacker-controlled.  */
-      && !ctxt->possibly_tainted_p (index_sval))
-    {
-      if (out)
-	*out = std::make_unique <rejected_default_case> (*this);
-      return false;
-    }
-
-  bounded_ranges_manager *ranges_mgr = get_range_manager ();
-  const bounded_ranges *all_cases_ranges
-    = ranges_mgr->get_or_create_ranges_for_switch (&edge, switch_stmt);
-  bool sat = m_constraints->add_bounded_ranges (index_sval, all_cases_ranges);
-  if (!sat && out)
-    *out = std::make_unique <rejected_ranges_constraint>
-      (*this, index, all_cases_ranges);
-  if (sat && ctxt && !all_cases_ranges->empty_p ())
-    ctxt->on_bounded_ranges (*index_sval, *all_cases_ranges);
-  return sat;
-}
-
-class rejected_eh_dispatch : public rejected_constraint
-{
-public:
-  rejected_eh_dispatch (const region_model &model)
-  : rejected_constraint (model)
-  {}
-
-  void dump_to_pp (pretty_printer *pp) const final override
-  {
-    pp_printf (pp, "rejected_eh_dispatch");
-  }
-};
-
-static bool
-exception_matches_type_p (tree exception_type,
-			  tree catch_type)
-{
-  if (catch_type == exception_type)
-    return true;
-
-  /* TODO (PR analyzer/119697): we should also handle subclasses etc;
-     see the rules in https://en.cppreference.com/w/cpp/language/catch
-
-     It looks like we should be calling (or emulating)
-     can_convert_eh from the C++ FE, but that's specific to the C++ FE.  */
-
-  return false;
-}
-
-static bool
-matches_any_exception_type_p (eh_catch ehc, tree exception_type)
-{
-  if (ehc->type_list == NULL_TREE)
-    /* All exceptions are caught here.  */
-    return true;
-
-  for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
-    if (exception_matches_type_p (TREE_VALUE (iter),
-				  exception_type))
-      return true;
-  return false;
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
-				   const geh_dispatch *,
-				   region_model_context *ctxt,
-				   std::unique_ptr<rejected_constraint> *out)
-{
-  const exception_node *current_node = get_current_thrown_exception ();
-  gcc_assert (current_node);
-  tree curr_exception_type = current_node->maybe_get_type ();
-  if (!curr_exception_type)
-    /* We don't know the specific type.  */
-    return true;
-
-  return edge.apply_constraints (this, ctxt, curr_exception_type, out);
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch_try (const eh_dispatch_try_cfg_superedge &edge,
-				       region_model_context */*ctxt*/,
-				       tree exception_type,
-				       std::unique_ptr<rejected_constraint> *out)
-{
-  /* TODO: can we rely on this ordering?
-     or do we need to iterate through prev_catch ?  */
-  /* The exception must not match any of the previous edges.  */
-  for (auto sibling_sedge : edge.m_src->m_succs)
-    {
-      if (sibling_sedge == &edge)
-	break;
-
-      const eh_dispatch_try_cfg_superedge *sibling_eh_sedge
-	= as_a <const eh_dispatch_try_cfg_superedge *> (sibling_sedge);
-      if (eh_catch ehc = sibling_eh_sedge->get_eh_catch ())
-	if (matches_any_exception_type_p (ehc, exception_type))
-	  {
-	    /* The earlier sibling matches, so the "unhandled" edge is
-	       not taken.  */
-	    if (out)
-	      *out = std::make_unique<rejected_eh_dispatch> (*this);
-	    return false;
-	  }
-    }
-
-  if (eh_catch ehc = edge.get_eh_catch ())
-    {
-      /* We have an edge that tried to match one or more types.  */
-
-      /* The exception must not match any of the previous edges.  */
-
-      /* It must match this type.  */
-      if (matches_any_exception_type_p (ehc, exception_type))
-	return true;
-      else
-	{
-	  /* Exception type doesn't match.  */
-	  if (out)
-	    *out = std::make_unique<rejected_eh_dispatch> (*this);
-	  return false;
-	}
-    }
-  else
-    {
-      /* This is the "unhandled exception" edge.
-	 If we get here then no sibling edges matched;
-	 we will follow this edge.  */
-      return true;
-    }
-}
-
-bool
-region_model::
-apply_constraints_for_eh_dispatch_allowed (const eh_dispatch_allowed_cfg_superedge &edge,
-					   region_model_context */*ctxt*/,
-					   tree exception_type,
-					   std::unique_ptr<rejected_constraint> *out)
-{
-  auto curr_thrown_exception_node = get_current_thrown_exception ();
-  gcc_assert (curr_thrown_exception_node);
-  tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
-  eh_region eh_reg = edge.get_eh_region ();
-  tree type_list = eh_reg->u.allowed.type_list;
-
-  switch (edge.get_eh_kind ())
-    {
-    default:
-      gcc_unreachable ();
-    case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
-      if (!curr_exception_type)
-	{
-	  /* We don't know the specific type;
-	     assume we have one of an expected type.  */
-	  return true;
-	}
-      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
-	if (exception_matches_type_p (TREE_VALUE (iter),
-				      exception_type))
-	  return true;
-      if (out)
-	*out = std::make_unique<rejected_eh_dispatch> (*this);
-      return false;
-
-    case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
-      if (!curr_exception_type)
-	{
-	  /* We don't know the specific type;
-	     assume we don't have one of an expected type.  */
-	  if (out)
-	    *out = std::make_unique<rejected_eh_dispatch> (*this);
-	  return false;
-	}
-      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
-	if (exception_matches_type_p (TREE_VALUE (iter),
-				      exception_type))
-	  {
-	    if (out)
-	      *out = std::make_unique<rejected_eh_dispatch> (*this);
-	    return false;
-	  }
-      return true;
-    }
-}
-
-/* Given an edge reached by GOTO_STMT, determine appropriate constraints
-   for the edge to be taken.
-
-   If they are feasible, add the constraints and return true.
-
-   Return false if the constraints contradict existing knowledge
-   (and so the edge should not be taken).  */
-
-bool
-region_model::apply_constraints_for_ggoto (const cfg_superedge &edge,
-					   const ggoto *goto_stmt,
-					   region_model_context *ctxt)
-{
-  tree dest = gimple_goto_dest (goto_stmt);
-  const svalue *dest_sval = get_rvalue (dest, ctxt);
-
-  /* If we know we were jumping to a specific label.  */
-  if (tree dst_label = edge.m_dest->get_label ())
-    {
-      const label_region *dst_label_reg
-	= m_mgr->get_region_for_label (dst_label);
-      const svalue *dst_label_ptr
-	= m_mgr->get_ptr_svalue (ptr_type_node, dst_label_reg);
-
-      if (!add_constraint (dest_sval, EQ_EXPR, dst_label_ptr, ctxt))
-	return false;
-    }
-
-  return true;
-}
-
 /* For use with push_frame when handling a top-level call within the analysis.
    PARAM has a defined but unknown initial value.
    Anything it points to has escaped, since the calling context "knows"
@@ -6896,46 +6396,21 @@ public:
       m_call_stmt (call_stmt),
       m_caller_frame (caller_frame)
   {}
-  bool warn (std::unique_ptr<pending_diagnostic> d,
-	     const stmt_finder *custom_finder) override
+
+  pending_location
+  get_pending_location_for_diag () const override
   {
-    if (m_inner && custom_finder == nullptr)
-      {
-	/* Custom stmt_finder to use m_call_stmt for the
-	   diagnostic.  */
-	class my_finder : public stmt_finder
-	{
-	public:
-	  my_finder (const gcall *call_stmt,
-		     const frame_region &caller_frame)
-	    : m_call_stmt (call_stmt),
-	      m_caller_frame (caller_frame)
-	  {}
-	  std::unique_ptr<stmt_finder> clone () const override
-	  {
-	    return std::make_unique<my_finder> (m_call_stmt, m_caller_frame);
-	  }
-	  const gimple *find_stmt (const exploded_path &) override
-	  {
-	    return m_call_stmt;
-	  }
-	  void update_event_loc_info (event_loc_info &loc_info) final override
-	  {
-	    loc_info.m_fndecl = m_caller_frame.get_fndecl ();
-	    loc_info.m_depth = m_caller_frame.get_stack_depth ();
-	  }
+    pending_location ploc
+      = region_model_context_decorator::get_pending_location_for_diag ();
 
-	private:
-	  const gcall *m_call_stmt;
-	  const frame_region &m_caller_frame;
-	};
-	my_finder finder (m_call_stmt, m_caller_frame);
-	return m_inner->warn (std::move (d), &finder);
-      }
-    else
-      return region_model_context_decorator::warn (std::move (d),
-						   custom_finder);
+    ploc.m_event_loc_info
+      = event_loc_info (m_call_stmt->location,
+			m_caller_frame.get_fndecl (),
+			m_caller_frame.get_stack_depth ());
+
+    return ploc;
   }
+
   const gimple *get_stmt () const override
   {
     return m_call_stmt;
@@ -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<pending_diagnostic> d,
+      std::unique_ptr<pending_location::fixer_for_epath> ploc_fixer)
+{
+  pending_location ploc (get_pending_location_for_diag ());
+  ploc.m_fixer_for_epath = std::move (ploc_fixer);
+  return warn_at (std::move (d), std::move (ploc));
+}
+
 /* class noop_region_model_context : public region_model_context.  */
 
 void
@@ -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 7f33a4572e72..091f0d0ac4cd 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<const svalue *> &svals_changing_meaning,
-		   region_model_context *ctxt);
-
-  bool maybe_update_for_edge (const superedge &edge,
-			      const gimple *last_stmt,
-			      region_model_context *ctxt,
-			      std::unique_ptr<rejected_constraint> *out);
-
   void update_for_gcall (const gcall &call_stmt,
 			 region_model_context *ctxt,
 			 function *callee = nullptr);
@@ -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<rejected_constraint> *out);
-
-  bool
-  apply_constraints_for_eh_dispatch_allowed
-    (const eh_dispatch_allowed_cfg_superedge &edge,
-     region_model_context *ctxt,
-     tree exception_type,
-     std::unique_ptr<rejected_constraint> *out);
-
 private:
   const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
   const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -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<rejected_constraint> *out);
-  bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
-				      const gswitch *switch_stmt,
-				      region_model_context *ctxt,
-				      std::unique_ptr<rejected_constraint> *out);
-  bool apply_constraints_for_ggoto (const cfg_superedge &edge,
-				    const ggoto *goto_stmt,
-				    region_model_context *ctxt);
-
-  bool
-  apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
-				     const geh_dispatch *eh_dispatch_stmt,
-				     region_model_context *ctxt,
-				     std::unique_ptr<rejected_constraint> *out);
-
   void poison_any_pointers_to_descendents (const region *reg,
 					   enum poison_kind pkind);
 
@@ -814,11 +767,19 @@ private:
 class region_model_context
 {
  public:
+  bool
+  warn (std::unique_ptr<pending_diagnostic> d,
+	std::unique_ptr<pending_location::fixer_for_epath> ploc_fixer = nullptr);
+
+  /* Hook for determining where diagnostics are to currently be emitted.  */
+  virtual pending_location
+  get_pending_location_for_diag () const = 0;
+
   /* Hook for clients to store pending diagnostics.
-     Return true if the diagnostic was stored, or false if it was deleted.
-     Optionally provide a custom stmt_finder.  */
-  virtual bool warn (std::unique_ptr<pending_diagnostic> d,
-		     const stmt_finder *custom_finder = nullptr) = 0;
+     Return true if the diagnostic was stored, or false if it was deleted.  */
+  virtual bool
+  warn_at (std::unique_ptr<pending_diagnostic> d,
+	   pending_location &&ploc) = 0;
 
   /* Hook for clients to add a note to the last previously stored
      pending diagnostic.  */
@@ -943,8 +904,17 @@ class region_model_context
 class noop_region_model_context : public region_model_context
 {
 public:
-  bool warn (std::unique_ptr<pending_diagnostic>,
-	     const stmt_finder *) override { return false; }
+  pending_location
+  get_pending_location_for_diag () const override
+  {
+    return pending_location ();
+  }
+  bool
+  warn_at (std::unique_ptr<pending_diagnostic>,
+	   pending_location &&) override
+  {
+    return false;
+  }
   void add_note (std::unique_ptr<pending_note>) override;
   void add_event (std::unique_ptr<checker_event>) override;
   void on_svalue_leak (const svalue *) override {}
@@ -1026,11 +996,21 @@ private:
 class region_model_context_decorator : public region_model_context
 {
  public:
-  bool warn (std::unique_ptr<pending_diagnostic> d,
-	     const stmt_finder *custom_finder) override
+  pending_location
+  get_pending_location_for_diag () const override
   {
     if (m_inner)
-      return m_inner->warn (std::move (d), custom_finder);
+      return m_inner->get_pending_location_for_diag ();
+    else
+      return pending_location ();
+  }
+
+  bool
+  warn_at (std::unique_ptr<pending_diagnostic> d,
+	   pending_location &&ploc) override
+  {
+    if (m_inner)
+      return m_inner->warn_at (std::move (d), std::move (ploc));
     else
       return false;
   }
@@ -1214,11 +1194,12 @@ protected:
 class annotating_context : public region_model_context_decorator
 {
 public:
-  bool warn (std::unique_ptr<pending_diagnostic> d,
-	     const stmt_finder *custom_finder) override
+  bool
+  warn_at (std::unique_ptr<pending_diagnostic> d,
+	   pending_location &&ploc) override
   {
     if (m_inner)
-      if (m_inner->warn (std::move (d), custom_finder))
+      if (m_inner->warn_at (std::move (d), std::move (ploc)))
 	{
 	  add_annotations ();
 	  return true;
@@ -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<pending_diagnostic> d,
-	     const stmt_finder *) final override
+  bool
+  warn_at (std::unique_ptr<pending_diagnostic> d,
+	   pending_location &&) final override
   {
     m_diagnostics.safe_push (d.release ());
     return true;
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 8f294b533983..bcbb34873ea3 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 b8ae0a1747eb..f694223bee9b 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<const gcall *> (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<fd_use_after_close>
 			    (*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<fd_use_without_check>
 				(*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<fd_access_mode_mismatch> (*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<fd_access_mode_mismatch> (*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<fd_leak> (*this, NULL_TREE, nullptr));
     }
 }
 
 void
-fd_state_machine::on_creat (sm_context &sm_ctxt, const supernode *node,
-			    const gimple *stmt, const gcall &call) const
+fd_state_machine::on_creat (sm_context &sm_ctxt, const gcall &call) const
 {
   tree lhs = gimple_call_lhs (&call);
   if (lhs)
-    sm_ctxt.on_transition (node, stmt, lhs, m_start, m_unchecked_write_only);
+    sm_ctxt.on_transition (lhs, m_start, m_unchecked_write_only);
   else
-    sm_ctxt.warn (node, stmt, NULL_TREE,
+    sm_ctxt.warn (NULL_TREE,
 		  std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
 }
 
 void
-fd_state_machine::check_for_dup (sm_context &sm_ctxt, const supernode *node,
-				 const gimple *stmt, const gcall &call,
+fd_state_machine::check_for_dup (sm_context &sm_ctxt, const gcall &call,
 				 const tree callee_fndecl, enum dup kind) const
 {
   tree lhs = gimple_call_lhs (&call);
   tree arg_1 = gimple_call_arg (&call, 0);
-  state_t state_arg_1 = sm_ctxt.get_state (stmt, arg_1);
+  state_t state_arg_1 = sm_ctxt.get_state (arg_1);
   if (state_arg_1 == m_stop)
     return;
   if (!(is_constant_fd_p (state_arg_1) || is_valid_fd_p (state_arg_1)
 	|| state_arg_1 == m_start))
     {
-      check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl,
-			 DIRS_READ_WRITE);
+      check_for_open_fd (sm_ctxt, call, callee_fndecl, DIRS_READ_WRITE);
       return;
     }
   switch (kind)
@@ -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<fd_use_without_check> (*this, diag_arg_2,
-						      callee_fndecl));
+	  sm_ctxt.warn
+	    (arg_2,
+	     std::make_unique<fd_use_without_check> (*this, diag_arg_2,
+						     callee_fndecl));
 	  return;
 	}
       /* dup2 returns value of its second argument on success.But, the
@@ -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<fd_double_close> (*this, diag_arg));
-      sm_ctxt.set_next_state (stmt, arg, m_stop);
+      sm_ctxt.set_next_state (arg, m_stop);
     }
 }
 void
-fd_state_machine::on_read (sm_context &sm_ctxt, const supernode *node,
-			   const gimple *stmt, const gcall &call,
+fd_state_machine::on_read (sm_context &sm_ctxt, const gcall &call,
 			   const tree callee_fndecl) const
 {
-  check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIRS_READ);
+  check_for_open_fd (sm_ctxt,call, callee_fndecl, DIRS_READ);
 }
 void
-fd_state_machine::on_write (sm_context &sm_ctxt, const supernode *node,
-			    const gimple *stmt, const gcall &call,
+fd_state_machine::on_write (sm_context &sm_ctxt, const gcall &call,
 			    const tree callee_fndecl) const
 {
-  check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIRS_WRITE);
+  check_for_open_fd (sm_ctxt,call, callee_fndecl, DIRS_WRITE);
 }
 
 void
 fd_state_machine::check_for_open_fd (
-    sm_context &sm_ctxt, const supernode *node, const gimple *stmt,
+    sm_context &sm_ctxt,
     const gcall &call, const tree callee_fndecl,
     enum access_directions callee_fndecl_dir) const
 {
   tree arg = gimple_call_arg (&call, 0);
   tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
-  state_t state = sm_ctxt.get_state (stmt, arg);
+  state_t state = sm_ctxt.get_state (arg);
 
   if (is_closed_fd_p (state))
     {
-      sm_ctxt.warn (node, stmt, arg,
+      sm_ctxt.warn (arg,
 		    std::make_unique<fd_use_after_close> (*this, diag_arg,
 							  callee_fndecl));
     }
@@ -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<fd_phase_mismatch> (*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<fd_use_without_check> (*this, diag_arg,
-							callee_fndecl));
+	    sm_ctxt.warn
+	      (arg,
+	       std::make_unique<fd_use_without_check> (*this, diag_arg,
+						       callee_fndecl));
 	}
       switch (callee_fndecl_dir)
 	{
@@ -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<fd_access_mode_mismatch>
 			      (*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<fd_access_mode_mismatch>
 			      (*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<fd_leak> (*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<fd_use_after_close> (*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<fd_type_mismatch> (*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<fd_use_without_check> (*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<fd_phase_mismatch> (*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<fd_phase_mismatch> (*this, diag_arg,
 						cd.get_fndecl_for_call (),
 						old_state,
 						EXPECTED_PHASE_CAN_LISTEN));
       else
 	sm_ctxt.warn
-	  (node, &call, fd_sval,
+	  (fd_sval,
 	   std::make_unique<fd_type_mismatch> (*this, diag_arg,
 					       cd.get_fndecl_for_call (),
 					       old_state,
@@ -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<fd_phase_mismatch> (*this, diag_arg,
 						cd.get_fndecl_for_call (),
 						old_state,
 						EXPECTED_PHASE_CAN_ACCEPT));
       else
 	sm_ctxt.warn
-	  (node, &call, fd_sval,
+	  (fd_sval,
 	   std::make_unique<fd_type_mismatch> (*this, diag_arg,
 					       cd.get_fndecl_for_call (),
 					       old_state,
@@ -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<fd_leak> (*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 c852c02804b1..eab8025d579a 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 <const gcall *> (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<double_fclose> (*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<kf_stdio_output_fn> ());
 
   kfm.add ("ferror", std::make_unique<kf_ferror> ());
+  kfm.add ("ferror_unlocked", std::make_unique<kf_ferror> ());
   kfm.add ("fgets", std::make_unique<kf_fgets> ());
   kfm.add ("fgets_unlocked", std::make_unique<kf_fgets> ()); // non-standard
   kfm.add ("fileno", std::make_unique<kf_fileno> ());
+  kfm.add ("fileno_unlocked", std::make_unique<kf_fileno> ());
   kfm.add ("fread", std::make_unique<kf_fread> ());
+  kfm.add ("fread_unlocked", std::make_unique<kf_fread> ());
   kfm.add ("getc", std::make_unique<kf_getc> ());
+  kfm.add ("getc_unlocked", std::make_unique<kf_getc> ());
   kfm.add ("getchar", std::make_unique<kf_getchar> ());
+  kfm.add ("getchar_unlocked", std::make_unique<kf_getchar> ());
 
   /* Some C++ implementations use the std:: copies of these functions
      from <cstdio> for <stdio.h>, so we must match against these too.  */
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 88bfc74888c6..7fa50fd7a563 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<possible_null_arg> (*this, diag_arg, fndecl,
 					      i));
       const allocation_state *astate
 	= as_a_allocation_state (state);
-      sm_ctxt.set_next_state (stmt, arg, astate->get_nonnull ());
+      sm_ctxt.set_next_state (arg, astate->get_nonnull ());
     }
   else if (state == m_null)
     {
       tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
-      sm_ctxt.warn (node, stmt, arg,
+      sm_ctxt.warn (arg,
 		    std::make_unique<null_arg> (*this, diag_arg, fndecl, i));
-      sm_ctxt.set_next_state (stmt, arg, m_stop);
+      sm_ctxt.set_next_state (arg, m_stop);
     }
   else if (state == m_start)
-    maybe_assume_non_null (sm_ctxt, arg, stmt);
+    maybe_assume_non_null (sm_ctxt, arg);
 }
 
 /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine.  */
 
 bool
 malloc_state_machine::on_stmt (sm_context &sm_ctxt,
-			       const supernode *node,
 			       const gimple *stmt) const
 {
   if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
@@ -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<possible_null_deref> (*this,
 								   diag_arg));
 	      const allocation_state *astate = as_a_allocation_state (state);
-	      sm_ctxt.set_next_state (stmt, arg, astate->get_nonnull ());
+	      sm_ctxt.set_next_state (arg, astate->get_nonnull ());
 	    }
 	  else if (state == m_null)
 	    {
 	      tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
-	      sm_ctxt.warn (node, stmt, arg,
+	      sm_ctxt.warn (arg,
 			    std::make_unique<null_deref> (*this, diag_arg));
-	      sm_ctxt.set_next_state (stmt, arg, m_stop);
+	      sm_ctxt.set_next_state (arg, m_stop);
 	    }
 	  else if (freed_p (state))
 	    {
 	      tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
 	      const allocation_state *astate = as_a_allocation_state (state);
-	      sm_ctxt.warn (node, stmt, arg,
+	      sm_ctxt.warn (arg,
 			    std::make_unique<use_after_free>
 			      (*this, diag_arg, astate->m_deallocator));
-	      sm_ctxt.set_next_state (stmt, arg, m_stop);
+	      sm_ctxt.set_next_state (arg, m_stop);
 	    }
 	}
     }
@@ -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<deref_before_check> (*this, diag_ptr));
-  sm_ctxt.set_next_state (stmt, ptr, m_stop);
+  sm_ctxt.set_next_state (ptr, m_stop);
 }
 
 /* Handle a call to an allocator.
@@ -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<free_of_non_heap>
 		  (*this, diag_arg, freed_reg, d->m_name));
-  sm_ctxt.set_next_state (&call, arg, m_stop);
+  sm_ctxt.set_next_state (arg, m_stop);
 }
 
 void
 malloc_state_machine::on_deallocator_call (sm_context &sm_ctxt,
-					   const supernode *node,
 					   const gcall &call,
 					   const deallocator *d,
 					   unsigned argno) const
@@ -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<mismatching_deallocation>
 			  (*this, diag_arg,
 			   astate->m_deallocators,
 			   d));
 	}
-      sm_ctxt.set_next_state (&call, arg, d->m_freed);
+      sm_ctxt.set_next_state (arg, d->m_freed);
     }
 
   /* Keep state "null" as-is, rather than transitioning to "freed";
@@ -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<double_free> (*this, diag_arg, d->m_name));
-      sm_ctxt.set_next_state (&call, arg, m_stop);
+      sm_ctxt.set_next_state (arg, m_stop);
     }
   else if (state == m_non_heap)
     {
       /* non-heap -> stop, with warning.  */
-      handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+      handle_free_of_non_heap (sm_ctxt, call, arg, d);
     }
 }
 
@@ -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<mismatching_deallocation>
 			  (*this, diag_arg,
 			   astate->m_deallocators, d));
-	  sm_ctxt.set_next_state (&call, arg, m_stop);
+	  sm_ctxt.set_next_state (arg, m_stop);
 	  if (path_context *path_ctxt = sm_ctxt.get_path_context ())
 	    path_ctxt->terminate_path ();
 	}
@@ -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<double_free> (*this, diag_arg, "free"));
-      sm_ctxt.set_next_state (&call, arg, m_stop);
+      sm_ctxt.set_next_state (arg, m_stop);
       if (path_context *path_ctxt = sm_ctxt.get_path_context ())
 	path_ctxt->terminate_path ();
     }
   else if (state == m_non_heap)
     {
       /* non-heap -> stop, with warning.  */
-      handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+      handle_free_of_non_heap (sm_ctxt, call, arg, d);
       if (path_context *path_ctxt = sm_ctxt.get_path_context ())
 	path_ctxt->terminate_path ();
     }
@@ -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 12449a1d48d1..b87a40c03328 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<pattern_match> (lhs_expr, op, rhs_cst));
     }
 }
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 4611b10e35a0..08620481487f 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<exposure_through_output_file> (*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 <const gcall *> (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 35ecde1d24bb..6bd00ab0f641 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<signal_unsafe_call>
 			     (*this, *call, callee_fndecl));
     }
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index f2a94e8a4f61..42057e08c608 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 o[...]

[diff truncated at 524288 bytes]


More information about the Gcc-cvs mailing list