Analyzer warnings relating to problematic values (e.g. by division
by zero) can be difficult for the user to understand.

Consider this example
(with -fanalyzer -fno-analyzer-state-merge -fno-analyzer-state-purge):

int
get_zero (void)
{
  return 0;
}

struct foo { int x; int y; };

void
init_foo (struct foo *f, int x, int y)
{
  f->x = x;
  f->y = y;
}

int
do_divide (struct foo *f)
{
  return f->x / f->y;
}

int
test (int flag, int flag_2, int flag_3)
{
  struct foo f;
  int a = 42;
  int b = get_zero ();
  init_foo (&f, a, b);
  return do_divide (&f);
}

Before this patch, we emit:

demo.c: In function ‘do_divide’:
demo.c:24:15: warning: division by zero [-Wanalyzer-div-by-zero]
   24 |   return f->x / f->y;
      |          ~~~~~^~~~~~
  ‘test’: events 1-2
    │
    │   28 | test (int flag, int flag_2, int flag_3)
    │      | ^~~~
    │      | |
    │      | (1) entry to ‘test’
    │......
    │   34 |   return do_divide (&f);
    │      |          ~~~~~~~~~~~~~~
    │      |          |
    │      |          (2) calling ‘do_divide’ from ‘test’
    │
    └──> ‘do_divide’: events 3-4
           │
           │   22 | do_divide (struct foo *f)
           │      | ^~~~~~~~~
           │      | |
           │      | (3) entry to ‘do_divide’
           │   23 | {
           │   24 |   return f->x / f->y;
           │      |          ~~~~~~~~~~~
           │      |               |
           │      |               (4) ⚠️  division by zero
           │

which doesn't convey where the zero value came from.

This patch adds various wording and new events to tracking where the
problematic value comes from.  diagnostic_manager::annotate_exploded_path walks
backwards from the final enode, building a chain of instances of a new
state_transition class hierarchy.  These state_transition instances are
associated with checker_events where possible (e.g. at function entry),
otherwise, new state_transition_events are added for them, highlighting
e.g. where the pertinent zero value is passed as a parameter.

With the patch, we emit:

demo.c: In function ‘do_divide’:
demo.c:24:15: warning: division by zero [-Wanalyzer-div-by-zero]
   24 |   return f->x / f->y;
      |          ~~~~~^~~~~~
  ‘test’: events 1-2
    │
    │   28 | test (int flag, int flag_2, int flag_3)
    │      | ^~~~
    │      | |
    │      | (1) entry to ‘test’
    │......
    │   32 |   int b = get_zero ();
    │      |           ~~~~~~~~~~~
    │      |           |
    │      |           (2) calling ‘get_zero’ from ‘test’
    │
    └──> ‘get_zero’: events 3-4
           │
           │    7 | get_zero (void)
           │      | ^~~~~~~~
           │      | |
           │      | (3) entry to ‘get_zero’
           │    8 | {
           │    9 |   return 0;
           │      |          ~
           │      |          |
           │      |          (4) zero value originates here
           │
    <──────┘
    │
  ‘test’: events 5-6
    │
    │   32 |   int b = get_zero ();
    │      |           ^~~~~~~~~~~
    │      |           |
    │      |           (5) returning zero from (4) from ‘get_zero’ here
    │   33 |   init_foo (&f, a, b);
    │      |   ~~~~~~~~~~~~~~~~~~~
    │      |   |
    │      |   (6) passing zero from (5) from ‘test’ to ‘init_foo’ via 
parameter 3
    │
    └──> ‘init_foo’: events 7-8
           │
           │   15 | init_foo (struct foo *f, int x, int y)
           │      |                                 ~~~~^
           │      |                                     |
           │      |                                     (7) entry to ‘init_foo’ 
with zero from (5) for ‘y’
           │......
           │   18 |   f->y = y;
           │      |   ~~~~~~~~
           │      |        |
           │      |        (8) copying zero value from (7) from ‘y’ to ‘*f.y’
           │
    <──────┘
    │
  ‘test’: events 9-10
    │
    │   33 |   init_foo (&f, a, b);
    │      |   ^~~~~~~~~~~~~~~~~~~
    │      |   |
    │      |   (9) returning to ‘test’ from ‘init_foo’
    │   34 |   return do_divide (&f);
    │      |          ~~~~~~~~~~~~~~
    │      |          |
    │      |          (10) calling ‘do_divide’ from ‘test’
    │
    └──> ‘do_divide’: events 11-13
           │
           │   22 | do_divide (struct foo *f)
           │      | ^~~~~~~~~
           │      | |
           │      | (11) entry to ‘do_divide’
           │   23 | {
           │   24 |   return f->x / f->y;
           │      |          ~~~~~~~~~~~
           │      |               |  |
           │      |               |  (12) using zero value from (8) from ‘*f.y’
           │      |               (13) ⚠️  division by zero
           │

Note:
* the new "(4) zero value originates here" state transition event,
* the precision-of-wording for the return event at (5),
* the precision-of-wording for the call event at (6),
* the precision-of-wording for the function entry event at (7), and the
  underlining of the pertinent parameter
* the above call/return events no longer get optimized away, due to...
* the new "(8) copying zero value from (7) from ‘y’ to ‘*f.y’" state
  transition event
* the new "(12) using zero value from (8) from ‘*f.y’" state transition
  event

Successfully bootstrapped & regrtested on aarch64-unknown-linux-gnu
Pushed to trunk as r17-359-g9a231dbb68ab3d.

gcc/ChangeLog:
        * Makefile.in (ANALYZER_OBJS): Add analyzer/state-transition.o.
        * digraph.cc (test_path::append_edge): New.
        (test_path::reverse): New.
        * shortest-paths.h (get_shortest_path): Use append_edge and
        reverse.
        * tree-diagnostic.h
        (tree_dump_pretty_printer::~tree_dump_pretty_printer): Only flush
        when the buffer's stream is non-null.

gcc/analyzer/ChangeLog:
        * analyzer.cc (printable_expr_p): New.
        * call-info.cc: Include "analyzer/state-transition.h".
        (call_info::add_events_to_path): Add state_transition param.
        * call-info.h (call_info::add_events_to_path): Likewise.
        * callsite-expr.h: New file, based on material from supergraph.h.
        (class callsite_expr_element): New.
        * checker-event.cc: Include "analyzer/callsite-expr.h" and
        "analyzer/state-transition.h".
        (event_kind_to_string): Handle event_kind::state_transition.
        (state_transition_event::print_desc): New.
        (state_transition_event::prepare_for_emission): New.
        (function_entry_event::function_entry_event): Drop.
        (function_entry_event::print_desc): Use any m_state_trans.
        (function_entry_event::prepare_for_emission): New.
        (call_event::call_event): Add "state_trans" param and store it in
        m_state_trans.
        (call_event::print_desc): Use m_state_trans if present to call
        describe_call_with_state, adding a fallback wording for
        pending_diagnostic subclasses that don't implement it.
        (call_event::prepare_for_emission): New, storing event id into
        state_transition.
        (return_event::return_event): Add "state_trans" param and store it
        in m_state_trans.
        (return_event::print_desc): Use m_state_trans if present to call
        describe_return_of_state.
        (return_event::prepare_for_emission): New, storing event id into
        state_transition.
        * checker-event.h (enum class event_kind): Add state_transition.
        (class state_transition_event): New.
        (function_entry_event::function_entry_event): Add "state_trans"
        param and store it in m_state_trans.  Drop 2nd ctor.
        (function_entry_event::prepare_for_emission): New decl.
        (function_entry_event::get_state_transition_at_call): New.
        (function_entry_event::m_state_trans): New.
        (call_event::call_event): Add "state_trans" param.
        (call_event::prepare_for_emission): New decl.
        (call_event::get_state_transition_at_call): New.
        (call_event::m_state_trans): New.
        (return_event::return_event): Add "state_trans" param.
        (return_event::prepare_for_emission): New decl.
        (return_event::m_state_trans): New.
        * common.h: Define INCLUDE_ALGORITHM.
        (class state_transition): New forward decl
        (class state_transition_at_call): New forward decl
        (class state_transition_at_return): New forward decl
        (printable_expr_p): New decl.
        (struct diagnostic_state): New.
        (struct rewind_context): New forward decl.
        (custom_edge_info::add_events_to_path): Add "state_trans" param.
        (try_to_rewind_data_flow): New vfunc.
        * diagnostic-manager.cc (compatible_epath_p): Update for change
        from m_edges to m_elements.
        (diagnostic_manager::emit_saved_diagnostic):  Make "epath"
        non-const.  Call annotate_exploded_path on it and log before and
        after.  Update for new param to add_events_for_eedge.
        (class epath_rewind_context): New.
        (diagnostic_manager::annotate_exploded_path): New.
        (diagnostic_manager::build_emission_path): Update for change from
        m_edges to m_elements.  Pass any state transition to
        add_events_for_eedge.
        (diagnostic_manager::add_events_for_eedge): Add "state_trans"
        parameter.  Pass it when creating events, and if we don't create
        an event referencing it, make a state_transition_event for it.
        (diagnostic_manager::prune_for_sm_diagnostic): Handl
        event_kind::state_transition.
        * diagnostic-manager.h (saved_diagnostic::get_best_epath): Drop
        "const" from return value.
        (diagnostic_manager::annotate_exploded_path): New decl.
        (diagnostic_manager::add_events_for_eedge): Add "state_trans"
        param.
        * engine.cc: Include "pretty-print-markup.h".
        (leak_ploc_fixer_for_epath::fixup_for_epath): Update for change
        from m_edges to m_elements.
        (throw_custom_edge::add_events_to_path): Add state_transition
        param.
        (unwind_custom_edge::add_events_to_path): Likewise.
        (interprocedural_call::print): Use get_gcall.
        (interprocedural_call::update_model): Likewise.
        (interprocedural_call::add_events_to_path): Likewise.  Pass
        state_transition if of correct subclass.
        (interprocedural_call::try_to_rewind_data_flow): New.
        (interprocedural_call::get_gcall): New.
        (interprocedural_return::add_events_to_path): Add state_transition
        param and pass if of correct subclass.
        (interprocedural_return::try_to_rewind_data_flow): New.
        (tainted_args_function_info::add_events_to_path): Add
        state_transition param.
        (tainted_args_call_info::add_events_to_path): Likewise.
        * event-loc-info.h (event_loc_info_for_function_entry): New decl.
        * exploded-graph.h (interprocedural_call::interprocedural_call):
        Pass and store call_and_return_op rather than gcall.
        (interprocedural_call::add_events_to_path): Add state_trans param.
        (interprocedural_call::try_to_rewind_data_flow): New.
        (interprocedural_call::m_call_stmt): Replace with...
        (interprocedural_call::m_op): ...this.
        (interprocedural_return::add_events_to_path): Add state_trans
        param.
        (interprocedural_return::try_to_rewind_data_flow): New.
        (rewind_info_t::add_events_to_path): New.
        * exploded-path.cc (exploded_path::exploded_path): Drop explicit
        copy ctor.
        (diagnostic_state::dump_to_pp): New.
        (diagnostic_state::dump): New.
        (exploded_path::find_stmt_backwards): Update for change from
        m_edges to m_elements.
        (exploded_path::get_final_enode): Likewise.
        (exploded_path::feasible_p): Likewise.
        (exploded_path::dump_to_pp): Likewise.  Dump state transitions.
        (exploded_path::maybe_log): New.
        (exploded_path::reverse): New.
        * exploded-path.h: Include "analyzer/checker-event.h" and
        "analyzer/state-transition.h".
        (struct exploded_path::element_t): New.
        (exploded_path::exploded_path): Replace ctors with "= default".
        (exploded_path::length): Reimplement.
        (exploded_path::maybe_log): New decl.
        (exploded_path::append_edge): New.
        (exploded_path::reverse): New.
        (exploded_path::m_edges): Replace with...
        (exploded_path::m_elements): ...this, using std::vector rather
        than auto_vec.
        * feasible-graph.cc (feasible_graph::make_epath): Update for
        change from m_edges to m_elements.
        * infinite-recursion.cc
        (infinite_recursion_diagnostic::add_function_entry_event): Add
        "state_trans" param and pass it to base class call.
        (recursive_function_entry_event::recursive_function_entry_event):
        Pass nullptr for state transition.
        * ops.cc: Include "analyzer/callsite-expr.h" and
        "analyzer/state-transition.h".
        (event_loc_info_for_function_entry): New.
        (rewind_context::on_data_origin): New.
        (rewind_context::on_data_flow): New.
        (gassign_op::try_to_rewind_data_flow): New.
        (greturn_op::try_to_rewind_data_flow): New.
        (call_and_return_op::execute): Update for change to
        interprocedural_call.
        (call_and_return_op::try_to_rewind_data_flow): New.
        (phis_for_edge_op::try_to_rewind_data_flow): New.
        * ops.h (struct rewind_context): New.
        (operation::try_to_rewind_data_flow): New vfunc.
        (gassign_op::try_to_rewind_data_flow): New.
        (predict_op::try_to_rewind_data_flow): New.
        (greturn_op::try_to_rewind_data_flow): New.
        (call_and_return_op::try_to_rewind_data_flow): New.
        (control_flow_op::try_to_rewind_data_flow): New.
        (phis_for_edge_op::try_to_rewind_data_flow): New.
        * pending-diagnostic.cc (interesting_t::add_read_region): New.
        (interesting_t::dump_to_pp): Dump m_read_regions.
        (pending_diagnostic::add_function_entry_event): Add "state_trans"
        param and pass it to function_entry_event.  Call
        event_loc_info_for_function_entry.
        (pending_diagnostic::add_call_event): Add "state_trans" param and
        pass it to call_event.
        * pending-diagnostic.h: Include "analyzer/state-transition.h".
        (struct interesting_t): Update leading comment.
        (interesting_t::add_read_region): New.
        (interesting_t::m_read_regions): New.
        (struct origin_of_state): New.
        (call_with_state::call_with_state): Add state_trans param and use
        it to initialize m_state_trans and m_src_event_id.
        (call_with_state::m_state_trans): New field.
        (call_with_state::m_src_event_id): New field.
        (return_of_state::return_of_state): Add state_trans param and use
        it to initialize m_state_trans and m_src_event_id.
        (return_of_state::m_state_trans): New field.
        (return_of_state::m_src_event_id): New field.
        (struct copy_of_state): New.
        (struct use_of_state): New.
        (pending_diagnostic::describe_origin_of_state): New vfunc.
        (pending_diagnostic::describe_copy_of_state): New vfunc.
        (pending_diagnostic::describe_use_of_state): New vfunc.
        (pending_diagnostic::add_function_entry_event): Add "state_trans"
        param.
        (pending_diagnostic::add_call_event): Likewise.
        * poisoned-value-diagnostic.cc
        (poisoned_value_diagnostic::mark_interesting_stuff): Add call to
        add_read_region.
        * region-model.cc: Include "analyzer/state-transition.h".
        (callsite_expr::maybe_get_param_location): New.
        (callsite_expr::get_param_tree): New.
        (div_by_zero_diagnostic::div_by_zero_diagnostic): Add
        "divisor_reg" param and use it to initialize m_divisor_reg.
        (div_by_zero_diagnostic::mark_interesting_stuff): New.
        (div_by_zero_diagnostic::add_function_entry_event): New.
        (div_by_zero_diagnostic::describe_origin_of_state): New.
        (div_by_zero_diagnostic::describe_call_with_state): New.
        (div_by_zero_diagnostic::describe_return_of_state): New.
        (div_by_zero_diagnostic::describe_copy_of_state): New.
        (div_by_zero_diagnostic::describe_use_of_state): New.
        (div_by_zero_diagnostic::m_divisor_reg): New field.
        (region_model::get_gassign_result): Pass region to calls to
        make_shift_count_negative_diagnostic,
        make_shift_count_overflow_diagnostic, and
        div_by_zero_diagnostic.
        (exception_thrown_from_unrecognized_call::add_events_to_path): Add
        state_transition param.
        * region-model.h (make_shift_count_negative_diagnostic): Add
        "src_region" param.
        (make_shift_count_overflow_diagnostic): Likewise.
        * region.h: Include "analyzer/store.h" and
        "text-art/tree-widget.h".
        * setjmp-longjmp.cc (rewind_info_t::add_events_to_path): Add
        state_transition param.
        * shift-diagnostics.cc
        (shift_count_negative_diagnostic::shift_count_negative_diagnostic):
        Add src_region param and use it to initialize m_src_region.
        (shift_count_negative_diagnostic::mark_interesting_stuff): New.
        (shift_count_negative_diagnostic::m_src_region): New.
        (make_shift_count_negative_diagnostic): Add src_region param.
        (shift_count_overflow_diagnostic::shift_count_overflow_diagnostic):
        Add src_region param and use it to initialize m_src_region.
        (shift_count_overflow_diagnostic::mark_interesting_stuff): New.
        (shift_count_overflow_diagnostic::m_src_region): New field.
        (make_shift_count_overflow_diagnostic): Add src_region param.
        * sm-signal.cc (signal_delivery_edge_info_t::add_events_to_path):
        Add state_transition param.
        * state-transition.cc: New file.
        * state-transition.h: New file.
        * supergraph.h (class callsite_expr): Move to callsite-expr.h.
        * varargs.cc (va_arg_diagnostic::add_call_event): Add state_trans
        param.

gcc/testsuite/ChangeLog:
        * c-c++-common/analyzer/divide-by-zero-1.c: Add expected message
        for origin of zero value.
        * c-c++-common/analyzer/divide-by-zero-2.c: New test.
        * c-c++-common/analyzer/divide-by-zero-3.c: New test.
        * c-c++-common/analyzer/invalid-shift-1.c: Add expected messages
        about call to op3 and about pertinent param of op3.
        * c-c++-common/analyzer/invalid-shift-2.c: New test.
        * c-c++-common/analyzer/invalid-shift-3.c: New test.
        * c-c++-common/analyzer/invalid-shift-4.c: New test.
        * g++.dg/analyzer/divide-by-zero-7.C: New test.
        * gcc.dg/analyzer/divide-by-zero-4.c: New test.
        * gcc.dg/analyzer/divide-by-zero-5.c: New test.
        * gcc.dg/analyzer/divide-by-zero-6.c: New test.
        * gcc.dg/analyzer/divide-by-zero-float.c: Add expected message
        for origin of zero value.
---
 gcc/Makefile.in                               |   1 +
 gcc/analyzer/analyzer.cc                      |  10 +
 gcc/analyzer/call-info.cc                     |   8 +-
 gcc/analyzer/call-info.h                      |   3 +-
 gcc/analyzer/callsite-expr.h                  | 104 ++++++++
 gcc/analyzer/checker-event.cc                 | 242 +++++++++++++++---
 gcc/analyzer/checker-event.h                  |  66 ++++-
 gcc/analyzer/common.h                         |  52 +++-
 gcc/analyzer/diagnostic-manager.cc            | 217 +++++++++++++++-
 gcc/analyzer/diagnostic-manager.h             |   9 +-
 gcc/analyzer/engine.cc                        | 118 +++++++--
 gcc/analyzer/event-loc-info.h                 |   4 +
 gcc/analyzer/exploded-graph.h                 |  23 +-
 gcc/analyzer/exploded-path.cc                 | 135 +++++++---
 gcc/analyzer/exploded-path.h                  |  58 ++++-
 gcc/analyzer/feasible-graph.cc                |   4 +-
 gcc/analyzer/infinite-recursion.cc            |  10 +-
 gcc/analyzer/ops.cc                           | 158 +++++++++++-
 gcc/analyzer/ops.h                            |  60 +++++
 gcc/analyzer/pending-diagnostic.cc            |  36 ++-
 gcc/analyzer/pending-diagnostic.h             | 129 +++++++++-
 gcc/analyzer/poisoned-value-diagnostic.cc     |   5 +-
 gcc/analyzer/region-model.cc                  | 215 +++++++++++++++-
 gcc/analyzer/region-model.h                   |   6 +-
 gcc/analyzer/region.h                         |   3 +-
 gcc/analyzer/setjmp-longjmp.cc                |   3 +-
 gcc/analyzer/shift-diagnostics.cc             |  36 ++-
 gcc/analyzer/sm-signal.cc                     |   3 +-
 gcc/analyzer/state-transition.cc              | 184 +++++++++++++
 gcc/analyzer/state-transition.h               | 191 ++++++++++++++
 gcc/analyzer/supergraph.h                     |  34 ---
 gcc/analyzer/varargs.cc                       |   8 +-
 gcc/digraph.cc                                |   3 +
 gcc/shortest-paths.h                          |   4 +-
 .../c-c++-common/analyzer/divide-by-zero-1.c  |   2 +-
 .../c-c++-common/analyzer/divide-by-zero-2.c  |  14 +
 .../c-c++-common/analyzer/divide-by-zero-3.c  |  19 ++
 .../c-c++-common/analyzer/invalid-shift-1.c   |   6 +-
 .../c-c++-common/analyzer/invalid-shift-2.c   |  11 +
 .../c-c++-common/analyzer/invalid-shift-3.c   |  12 +
 .../c-c++-common/analyzer/invalid-shift-4.c   |  12 +
 .../g++.dg/analyzer/divide-by-zero-7.C        |  28 ++
 .../gcc.dg/analyzer/divide-by-zero-4.c        |  39 +++
 .../gcc.dg/analyzer/divide-by-zero-5.c        |  42 +++
 .../gcc.dg/analyzer/divide-by-zero-6.c        |  27 ++
 .../gcc.dg/analyzer/divide-by-zero-float.c    |   2 +-
 gcc/tree-diagnostic.h                         |   3 +-
 47 files changed, 2153 insertions(+), 206 deletions(-)
 create mode 100644 gcc/analyzer/callsite-expr.h
 create mode 100644 gcc/analyzer/state-transition.cc
 create mode 100644 gcc/analyzer/state-transition.h
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/divide-by-zero-2.c
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/divide-by-zero-3.c
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/invalid-shift-2.c
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/invalid-shift-3.c
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/invalid-shift-4.c
 create mode 100644 gcc/testsuite/g++.dg/analyzer/divide-by-zero-7.C
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/divide-by-zero-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/divide-by-zero-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/divide-by-zero-6.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 5e656c3c25b..0ff15046f56 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1383,6 +1383,7 @@ ANALYZER_OBJS = \
        analyzer/sm-signal.o \
        analyzer/sm-taint.o \
        analyzer/state-purge.o \
+       analyzer/state-transition.o \
        analyzer/store.o \
        analyzer/supergraph.o \
        analyzer/supergraph-fixup-locations.o \
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index cf9d822629d..15c0c786a06 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -29,6 +29,16 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+bool
+printable_expr_p (const_tree expr)
+{
+  if (TREE_CODE (expr) == SSA_NAME
+      && !SSA_NAME_VAR (expr))
+    return false;
+
+  return true;
+}
+
 /* Workaround for missing location information for some stmts,
    which ultimately should be solved by fixing the frontends
    to provide the locations (TODO).  */
diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc
index 10022b43f34..b5b3fb5a53e 100644
--- a/gcc/analyzer/call-info.cc
+++ b/gcc/analyzer/call-info.cc
@@ -43,6 +43,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/exploded-graph.h"
 #include "analyzer/call-details.h"
 #include "analyzer/call-info.h"
+#include "analyzer/state-transition.h"
 
 #if ENABLE_ANALYZER
 
@@ -96,7 +97,8 @@ call_info::print (pretty_printer *pp) const
 void
 call_info::add_events_to_path (checker_path *emission_path,
                               const exploded_edge &eedge,
-                              pending_diagnostic &) const
+                              pending_diagnostic &,
+                              const state_transition *) const
 {
   class call_event : public custom_event
   {
@@ -121,6 +123,10 @@ call_info::add_events_to_path (checker_path *emission_path,
   tree caller_fndecl = src_point.get_fndecl ();
   const int stack_depth = src_point.get_stack_depth ();
 
+  /* TODO: we don't yet make use of the state_transition (if any), as
+     doing so presumably requires a combinatorial explosion of
+     known functions vs pending diagnostics.  */
+
   emission_path->add_event
     (std::make_unique<call_event> (event_loc_info (get_call_stmt ().location,
                                                   caller_fndecl,
diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h
index 0a18e4b04f8..d25c51d0ca4 100644
--- a/gcc/analyzer/call-info.h
+++ b/gcc/analyzer/call-info.h
@@ -33,7 +33,8 @@ public:
   void print (pretty_printer *pp) const override;
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &pd) const override;
+                          pending_diagnostic &pd,
+                          const state_transition *state_trans) const override;
 
   const gcall &get_call_stmt () const { return m_call_stmt; }
   tree get_fndecl () const { return m_fndecl; }
diff --git a/gcc/analyzer/callsite-expr.h b/gcc/analyzer/callsite-expr.h
new file mode 100644
index 00000000000..7c7e2d3897f
--- /dev/null
+++ b/gcc/analyzer/callsite-expr.h
@@ -0,0 +1,104 @@
+/* User-facing descriptions of expressions at call sites.
+   Copyright (C) 2019-2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+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_CALLSITE_EXPR_H
+#define GCC_ANALYZER_CALLSITE_EXPR_H
+
+#include "pretty-print-markup.h"
+
+namespace ana {
+
+/* An ID representing an expression at a callsite:
+   either a parameter index, or the return value (or unknown).  */
+
+class callsite_expr
+{
+ public:
+  callsite_expr () : m_val (-1) {}
+
+  static callsite_expr from_zero_based_param (int idx)
+  {
+    return callsite_expr (idx + 1);
+  }
+
+  static callsite_expr from_return_value ()
+  {
+    return callsite_expr (0);
+  }
+
+  bool param_p () const
+  {
+    return m_val > 0;
+  }
+
+  /* Get 1-based param number.  */
+  int param_num () const
+  {
+    gcc_assert (param_p ());
+    return m_val;
+  }
+
+  tree get_param_tree (tree fndecl) const;
+
+  bool
+  maybe_get_param_location (tree fndecl,
+                           location_t *out_loc) const;
+
+  bool return_value_p () const
+  {
+    return m_val == 0;
+  }
+
+ private:
+  callsite_expr (int val) : m_val (val) {}
+
+  int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown".  */
+};
+
+class callsite_expr_element : public pp_element
+{
+public:
+  callsite_expr_element (callsite_expr expr) : m_expr (expr) {}
+
+  void
+  add_to_phase_2 (pp_markup::context &ctxt) final override
+  {
+
+    if (m_expr.return_value_p ())
+      pp_string (&ctxt.m_pp, "return value");
+    else if (m_expr.param_p ())
+      {
+       /* We can't call pp_printf directly on ctxt.m_pp from within
+          formatting.  As a workaround, work with a clone of the pp.  */
+       std::unique_ptr<pretty_printer> pp (ctxt.m_pp.clone ());
+       pp_printf (pp.get (), "parameter %i", m_expr.param_num ());
+       pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ()));
+      }
+    else
+      pp_string (&ctxt.m_pp, "unknown");
+  }
+
+private:
+  callsite_expr m_expr;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_CALLSITE_EXPR_H */
diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc
index d1e9b4429f9..7e79727f5e6 100644
--- a/gcc/analyzer/checker-event.cc
+++ b/gcc/analyzer/checker-event.cc
@@ -45,6 +45,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/constraint-manager.h"
 #include "analyzer/checker-event.h"
 #include "analyzer/exploded-graph.h"
+#include "analyzer/callsite-expr.h"
+#include "analyzer/state-transition.h"
 
 #if ENABLE_ANALYZER
 
@@ -67,6 +69,8 @@ event_kind_to_string (enum event_kind ek)
       return "stmt";
     case event_kind::region_creation:
       return "region_creation";
+    case event_kind::state_transition:
+      return "state_transition";
     case event_kind::function_entry:
       return "function_entry";
     case event_kind::state_change:
@@ -301,6 +305,94 @@ statement_event::print_desc (pretty_printer &pp) const
   pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0);
 }
 
+/* class state_transition_event : public checker_event.  */
+
+void
+state_transition_event::print_desc (pretty_printer &pp) const
+{
+  gcc_assert (m_state_trans);
+  switch (m_state_trans->get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case state_transition::kind::origin:
+      {
+       const state_transition_origin &state_trans
+         = *static_cast <const state_transition_origin *> (m_state_trans);
+       if (m_pending_diagnostic)
+         {
+           evdesc::origin_of_state evd (state_trans.m_dst_reg_expr);
+           if (m_pending_diagnostic->describe_origin_of_state (pp, evd))
+             return;
+         }
+       pp_printf (&pp, "value originates here");
+      }
+      break;
+
+    case state_transition::kind::at_call:
+    case state_transition::kind::at_return:
+      // These should be handled by call_event and return_event
+      gcc_unreachable ();
+      break;
+
+    case state_transition::kind::copy:
+      {
+       const state_transition_copy &state_trans
+         = *static_cast <const state_transition_copy *> (m_state_trans);
+       if (m_pending_diagnostic)
+         {
+           evdesc::copy_of_state evd (state_trans.m_src_reg_expr,
+                                      state_trans.get_src_event_id (),
+                                      state_trans.m_dst_reg_expr);
+           if (m_pending_diagnostic->describe_copy_of_state (pp, evd))
+             return;
+         }
+       auto event_id = state_trans.get_src_event_id ();
+       if (event_id.known_p ())
+         pp_printf (&pp, "copying value from %@ from %qE to %qE",
+                    &event_id,
+                    state_trans.m_src_reg_expr,
+                    state_trans.m_dst_reg_expr);
+       else
+         pp_printf (&pp, "copying value from %qE to %qE",
+                    state_trans.m_src_reg_expr,
+                    state_trans.m_dst_reg_expr);
+      }
+      break;
+    case state_transition::kind::use:
+      {
+       const state_transition_use &state_trans
+         = *static_cast <const state_transition_use *> (m_state_trans);
+       if (m_pending_diagnostic)
+         {
+           evdesc::use_of_state evd (state_trans.m_src_reg_expr,
+                                     state_trans.get_src_event_id ());
+           if (m_pending_diagnostic->describe_use_of_state (pp, evd))
+             return;
+         }
+       auto event_id = state_trans.get_src_event_id ();
+       if (state_trans.get_src_event_id ().known_p ())
+         pp_printf (&pp, "using value from %@ from %qE",
+                    &event_id,
+                    state_trans.m_src_reg_expr);
+       else
+         pp_printf (&pp, "using value from %qE",
+                    state_trans.m_src_reg_expr);
+      }
+      break;
+    }
+}
+
+void
+state_transition_event::
+prepare_for_emission (checker_path *path,
+                     pending_diagnostic *pd,
+                     diagnostics::paths::event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  const_cast<state_transition *> (m_state_trans)->m_event_id = emission_id;
+}
+
 /* class region_creation_event : public checker_event.  */
 
 region_creation_event::region_creation_event (const event_loc_info &loc_info)
@@ -376,16 +468,6 @@ region_creation_event_debug::print_desc (pretty_printer 
&pp) const
 
 /* class function_entry_event : public checker_event.  */
 
-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_location (),
-                                dst_point.get_fndecl (),
-                                dst_point.get_stack_depth ())),
-  m_state (state)
-{
-}
-
 /* Implementation of diagnostics::paths::event::print_desc vfunc for
    function_entry_event.
 
@@ -394,6 +476,25 @@ function_entry_event::function_entry_event (const 
program_point &dst_point,
 void
 function_entry_event::print_desc (pretty_printer &pp) const
 {
+  if (m_state_trans)
+    {
+      callsite_expr expr = m_state_trans->get_callsite_expr ();
+      if (tree parm = expr.get_param_tree (m_effective_fndecl))
+       {
+         auto src_event_id = m_state_trans->get_src_event_id ();
+         if (src_event_id.known_p ())
+           pp_printf (&pp,
+                      "entry to %qE with problematic value from %@ for %qE",
+                      m_effective_fndecl,
+                      &src_event_id,
+                      parm);
+         else
+           pp_printf (&pp, "entry to %qE with problematic value for %qE",
+                      m_effective_fndecl, parm);
+         return;
+       }
+    }
+
   pp_printf (&pp, "entry to %qE", m_effective_fndecl);
 }
 
@@ -406,6 +507,17 @@ function_entry_event::get_meaning () const
   return meaning (verb::enter, noun::function);
 }
 
+void
+function_entry_event::
+prepare_for_emission (checker_path *path,
+                     pending_diagnostic *pd,
+                     diagnostics::paths::event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  if (m_state_trans)
+    const_cast<state_transition_at_call *> (m_state_trans)->m_event_id = 
emission_id;
+}
+
 /* class state_change_event : public checker_event.  */
 
 /* state_change_event's ctor.  */
@@ -730,8 +842,10 @@ catch_cfg_edge_event::get_meaning () const
 /* call_event's ctor.  */
 
 call_event::call_event (const exploded_edge &eedge,
-                       const event_loc_info &loc_info)
-: superedge_event (event_kind::call_, eedge, loc_info)
+                       const event_loc_info &loc_info,
+                       const state_transition_at_call *state_trans)
+: superedge_event (event_kind::call_, eedge, loc_info),
+  m_state_trans (state_trans)
 {
    m_src_snode = eedge.m_src->get_supernode ();
    m_dest_snode = eedge.m_dest->get_supernode ();
@@ -751,16 +865,50 @@ call_event::call_event (const exploded_edge &eedge,
 void
 call_event::print_desc (pretty_printer &pp) const
 {
-  if (m_critical_state.m_state && m_pending_diagnostic)
+  if (m_pending_diagnostic)
     {
-      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_state);
-      if (m_pending_diagnostic->describe_call_with_state (pp, evd))
-       return;
+      if (m_critical_state.m_state)
+       {
+         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_state,
+                                      m_state_trans);
+         if (m_pending_diagnostic->describe_call_with_state (pp, evd))
+           return;
+       }
+      else if (m_state_trans)
+       {
+         evdesc::call_with_state evd (m_src_snode->m_fun->decl,
+                                      m_dest_snode->m_fun->decl,
+                                      NULL_TREE, nullptr,
+                                      m_state_trans);
+         if (m_pending_diagnostic->describe_call_with_state (pp, evd))
+           return;
+         callsite_expr expr = m_state_trans->get_callsite_expr ();
+         if (expr.param_p ())
+           {
+             auto src_event_id = m_state_trans->get_src_event_id ();
+             if (src_event_id.known_p ())
+               pp_printf (&pp,
+                          "passing problematic value from %@ from %qE to %qE"
+                          " via parameter %i",
+                          &src_event_id,
+                          get_caller_fndecl (),
+                          get_callee_fndecl (),
+                          expr.param_num ());
+             else
+               pp_printf (&pp,
+                          "passing problematic value from %qE to %qE"
+                          " via parameter %i",
+                          get_caller_fndecl (),
+                          get_callee_fndecl (),
+                          expr.param_num ());
+             return;
+           }
+       }
     }
 
   pp_printf (&pp,
@@ -806,14 +954,26 @@ call_event::get_program_state () const
   return &m_eedge.m_src->get_state ();
 }
 
+void
+call_event::prepare_for_emission (checker_path *path,
+                                 pending_diagnostic *pd,
+                                 diagnostics::paths::event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  if (m_state_trans)
+    const_cast<state_transition_at_call *> (m_state_trans)->m_event_id = 
emission_id;
+}
+
 /* class return_event : public checker_event.  */
 
 /* return_event's ctor.  */
 
 return_event::return_event (const exploded_edge &eedge,
-                           const event_loc_info &loc_info)
+                           const event_loc_info &loc_info,
+                           const state_transition_at_return *state_trans)
 : checker_event (event_kind::return_, loc_info),
-  m_eedge (eedge)
+  m_eedge (eedge),
+  m_state_trans (state_trans)
 {
   m_src_snode = eedge.m_src->get_supernode ();
   m_dest_snode = eedge.m_dest->get_supernode ();
@@ -839,14 +999,28 @@ 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_state && m_pending_diagnostic)
+  if (m_pending_diagnostic)
     {
-      evdesc::return_of_state evd (m_dest_snode->m_fun->decl,
-                                  m_src_snode->m_fun->decl,
-                                  m_critical_state.m_state);
-      if (m_pending_diagnostic->describe_return_of_state (pp, evd))
-       return;
+      if (m_critical_state.m_state)
+       {
+         evdesc::return_of_state evd (m_dest_snode->m_fun->decl,
+                                      m_src_snode->m_fun->decl,
+                                      m_critical_state.m_state,
+                                      nullptr);
+         if (m_pending_diagnostic->describe_return_of_state (pp, evd))
+           return;
+       }
+      else if (m_state_trans)
+       {
+         evdesc::return_of_state evd (m_dest_snode->m_fun->decl,
+                                      m_src_snode->m_fun->decl,
+                                      nullptr,
+                                      m_state_trans);
+         if (m_pending_diagnostic->describe_return_of_state (pp, evd))
+           return;
+       }
     }
+
   pp_printf (&pp,
             "returning to %qE from %qE",
             m_dest_snode->m_fun->decl,
@@ -876,6 +1050,16 @@ return_event::get_program_state () const
   return &m_eedge.m_dest->get_state ();
 }
 
+void
+return_event::prepare_for_emission (checker_path *path,
+                                   pending_diagnostic *pd,
+                                   diagnostics::paths::event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  if (m_state_trans)
+    const_cast<state_transition_at_return *> (m_state_trans)->m_event_id = 
emission_id;
+}
+
 /* class start_consolidated_cfg_edges_event : public checker_event.  */
 
 void
diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h
index be39a89a7b1..edf81e907eb 100644
--- a/gcc/analyzer/checker-event.h
+++ b/gcc/analyzer/checker-event.h
@@ -37,6 +37,7 @@ enum class event_kind
   custom,
   stmt,
   region_creation,
+  state_transition,
   function_entry,
   state_change,
   start_cfg_edge,
@@ -68,6 +69,7 @@ extern const char *event_kind_to_string (enum event_kind ek);
        custom_event (event_kind::custom)
         precanned_custom_event
        statement_event (event_kind::stmt)
+       state_transition_event (event_kind::data_flow)
        region_creation_event (event_kind::region_creation)
        function_entry_event (event_kind::function_entry)
        state_change_event (event_kind::state_change)
@@ -245,6 +247,31 @@ public:
   const program_state m_dst_state;
 };
 
+/* A concrete checker_event subclass referencing a state_transition,
+   for cases where the state_transition doesn't already have its own event.  */
+
+class state_transition_event : public checker_event
+{
+public:
+  state_transition_event (const event_loc_info &loc_info,
+                         const state_transition *state_trans)
+  : checker_event (event_kind::state_transition, loc_info),
+    m_state_trans (state_trans)
+  {
+    gcc_assert (m_state_trans);
+  }
+
+  void print_desc (pretty_printer &) const final override;
+
+  void prepare_for_emission (checker_path *path,
+                            pending_diagnostic *pd,
+                            diagnostics::paths::event_id_t emission_id) final 
override;
+
+private:
+  // borrowed from the exploded_path
+  const state_transition *m_state_trans;
+};
+
 /* An abstract event subclass describing the creation of a region that
    is significant for a diagnostic.
 
@@ -352,15 +379,14 @@ class function_entry_event : public checker_event
 {
 public:
   function_entry_event (const event_loc_info &loc_info,
-                       const program_state &state)
+                       const program_state &state,
+                       const state_transition_at_call *state_trans)
   : checker_event (event_kind::function_entry, loc_info),
-    m_state (state)
+    m_state (state),
+    m_state_trans (state_trans)
   {
   }
 
-  function_entry_event (const program_point &dst_point,
-                       const program_state &state);
-
   void print_desc (pretty_printer &pp) const override;
   meaning get_meaning () const override;
 
@@ -372,8 +398,17 @@ public:
     return &m_state;
   }
 
+  void
+  prepare_for_emission (checker_path *path,
+                       pending_diagnostic *pd,
+                       diagnostics::paths::event_id_t emission_id) final 
override;
+
+  const state_transition_at_call *
+  get_state_transition_at_call () const { return m_state_trans; }
+
 private:
   const program_state &m_state;
+  const state_transition_at_call *m_state_trans;
 };
 
 /* Subclass of checker_event describing a state change.  */
@@ -558,7 +593,8 @@ class call_event : public superedge_event
 {
 public:
   call_event (const exploded_edge &eedge,
-             const event_loc_info &loc_info);
+             const event_loc_info &loc_info,
+             const state_transition_at_call *state_trans);
 
   void print_desc (pretty_printer &pp) const override;
   meaning get_meaning () const override;
@@ -568,6 +604,11 @@ public:
   const program_state *
   get_program_state () const final override;
 
+  void
+  prepare_for_emission (checker_path *path,
+                       pending_diagnostic *pd,
+                       diagnostics::paths::event_id_t emission_id) 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
@@ -577,6 +618,9 @@ public:
     m_critical_state = critical_state (var, state);
   }
 
+  const state_transition_at_call *
+  get_state_transition_at_call () const { return m_state_trans; }
+
 protected:
   tree get_caller_fndecl () const;
   tree get_callee_fndecl () const;
@@ -584,6 +628,7 @@ protected:
   const supernode *m_src_snode;
   const supernode *m_dest_snode;
   critical_state m_critical_state;
+  const state_transition_at_call *m_state_trans;
 };
 
 /* A concrete event subclass for an interprocedural return.  */
@@ -592,7 +637,8 @@ class return_event : public checker_event
 {
 public:
   return_event (const exploded_edge &eedge,
-               const event_loc_info &loc_info);
+               const event_loc_info &loc_info,
+               const state_transition_at_return *state_trans);
 
   void print_desc (pretty_printer &pp) const final override;
   meaning get_meaning () const override;
@@ -608,6 +654,11 @@ public:
   const program_state *
   get_program_state () const override;
 
+  void
+  prepare_for_emission (checker_path *path,
+                       pending_diagnostic *pd,
+                       diagnostics::paths::event_id_t emission_id) 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
@@ -622,6 +673,7 @@ public:
   const supernode *m_dest_snode;
   const call_and_return_op *m_call_and_return_op;
   critical_state m_critical_state;
+  const state_transition_at_return *m_state_trans;
 };
 
 /* 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 cbe36dfb9c9..81db5997f2e 100644
--- a/gcc/analyzer/common.h
+++ b/gcc/analyzer/common.h
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_COMMON_H
 
 #include "config.h"
+#define INCLUDE_ALGORITHM
 #define INCLUDE_LIST
 #define INCLUDE_MAP
 #define INCLUDE_SET
@@ -139,6 +140,9 @@ class call_summary;
 class call_summary_replay;
 struct per_function_data;
 struct interesting_t;
+class state_transition;
+  class state_transition_at_call;
+  class state_transition_at_return;
 class uncertainty_t;
 
 class feasible_node;
@@ -155,6 +159,7 @@ extern void dump_tree (pretty_printer *pp, tree t);
 extern void dump_quoted_tree (pretty_printer *pp, tree t);
 extern void print_quoted_type (pretty_printer *pp, tree t);
 extern void print_expr_for_user (pretty_printer *pp, tree t);
+extern bool printable_expr_p (const_tree expr);
 extern int readability_comparator (const void *p1, const void *p2);
 extern int tree_cmp (const void *p1, const void *p2);
 extern tree fixup_tree_for_diagnostic (tree);
@@ -374,6 +379,44 @@ enum class access_direction
   write
 };
 
+/* State tracked along an execution path that's pertinent to a specific
+   diagnostic (e.g. for a divide-by-zero warning where the zero value
+   comes from).  */
+
+struct diagnostic_state
+{
+  diagnostic_state ()
+  : m_region_holding_value (nullptr)
+  {
+  }
+
+  diagnostic_state (std::string debug_desc,
+                  const region *region_holding_value)
+  : m_debug_desc (std::move (debug_desc)),
+    m_region_holding_value (region_holding_value)
+  {
+  }
+
+  void dump_to_pp (pretty_printer *) const;
+  void dump () const;
+
+  bool
+  operator== (const diagnostic_state &other) const
+  {
+    return m_region_holding_value == other.m_region_holding_value;
+  }
+  bool
+  operator!= (const diagnostic_state &other) const
+  {
+    return !(*this == other);
+  }
+
+  std::string m_debug_desc;
+  const region *m_region_holding_value;
+};
+
+struct rewind_context;
+
 /* Abstract base class for associating custom data with an
    exploded_edge, for handling non-standard edges such as
    rewinding from a longjmp, signal handlers, etc.
@@ -406,13 +449,20 @@ public:
 
   virtual void add_events_to_path (checker_path *emission_path,
                                   const exploded_edge &eedge,
-                                  pending_diagnostic &pd) const = 0;
+                                  pending_diagnostic &pd,
+                                  const state_transition *state_trans) const = 
0;
 
   virtual exploded_node *create_enode (exploded_graph &eg,
                                       const program_point &point,
                                       program_state &&state,
                                       exploded_node *enode_for_diag,
                                       region_model_context *ctxt) const;
+
+  virtual bool
+  try_to_rewind_data_flow (rewind_context &) const
+  {
+    return false;
+  }
 };
 
 /* Abstract base class for splitting state.
diff --git a/gcc/analyzer/diagnostic-manager.cc 
b/gcc/analyzer/diagnostic-manager.cc
index a0f149e9c52..36ce445cf8c 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -917,7 +917,7 @@ compatible_epath_p (const exploded_path *lhs_path,
       while (lhs_eedge_idx >= 0)
        {
          /* Find LHS_PATH's next superedge.  */
-         lhs_eedge = lhs_path->m_edges[lhs_eedge_idx];
+         lhs_eedge = lhs_path->m_elements[lhs_eedge_idx].m_eedge;
          if (lhs_eedge->m_sedge)
            break;
          else
@@ -926,7 +926,7 @@ compatible_epath_p (const exploded_path *lhs_path,
       while (rhs_eedge_idx >= 0)
        {
          /* Find RHS_PATH's next superedge.  */
-         rhs_eedge = rhs_path->m_edges[rhs_eedge_idx];
+         rhs_eedge = rhs_path->m_elements[rhs_eedge_idx].m_eedge;
          if (rhs_eedge->m_sedge)
            break;
          else
@@ -1555,13 +1555,20 @@ diagnostic_manager::emit_saved_diagnostic (const 
exploded_graph &eg,
        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 ();
+  exploded_path *epath = sd.get_best_epath ();
   gcc_assert (epath);
 
+  epath->maybe_log (get_logger (), "best epath");
+
   /* Precompute all enodes from which the diagnostic is reachable.  */
   path_builder pb (eg, *epath, sd.get_feasibility_problem (), sd);
 
-  /* This is the diagnostics::paths::path subclass that will be built for
+  /* Annotate EPATH with information specific to the diagnostic, such
+     as pertinent data flow events.  */
+  annotate_exploded_path (pb, *epath);
+  epath->maybe_log (get_logger (), "best epath with annotations");
+
+  /* This is the diagnostics::paths::path instance that will be built for
      the diagnostic.  */
   checker_path emission_path (get_logical_location_manager (),
                              eg.get_ext_state (),
@@ -1589,7 +1596,8 @@ diagnostic_manager::emit_saved_diagnostic (const 
exploded_graph &eg,
      trailing eedge stashed, add any events for it.  This is for use
      in handling longjmp, to show where a longjmp is rewinding to.  */
   if (sd.m_trailing_eedge)
-    add_events_for_eedge (pb, *sd.m_trailing_eedge, &emission_path, nullptr);
+    add_events_for_eedge (pb, *sd.m_trailing_eedge, &emission_path, nullptr,
+                         nullptr);
 
   emission_path.inject_any_inlined_call_events (get_logger ());
 
@@ -1640,6 +1648,149 @@ diagnostic_manager::get_logical_location_manager () 
const
   return *mgr;
 }
 
+class epath_rewind_context : public rewind_context
+{
+public:
+  epath_rewind_context (const exploded_edge &eedge,
+                       logger *logger,
+                       diagnostic_state input_state,
+                       state_transition *&last_state_transition,
+                       exploded_path::element_t &epath_element)
+  : rewind_context (eedge, logger, input_state),
+    m_last_state_transition (last_state_transition),
+    m_epath_element (epath_element)
+  {
+  }
+
+  bool
+  could_be_affected_by_write_p (tree lhs) final override
+  {
+    if (!m_input.m_region_holding_value)
+      return false;
+
+    if (TREE_CODE (lhs) == SSA_NAME)
+      if (tree decl = m_input.m_region_holding_value->maybe_get_decl ())
+       return decl == lhs;
+
+    return true;
+  }
+
+  void
+  add_state_transition (std::unique_ptr<state_transition> st) final override
+  {
+    gcc_assert (st.get ());
+    if (m_logger)
+      {
+       m_logger->start_log_line ();
+       m_logger->log_partial ("adding state transition: ");
+       st->dump_to_pp (m_logger->get_printer ());
+       m_logger->end_log_line ();
+      }
+
+    /* Chain up the state_transition instances, so that each state transition
+       has a pointer to the one that occurred before it (but was created after
+       it, since we are rewinding the epath).  */
+    if (m_last_state_transition)
+      m_last_state_transition->m_prev_state_transition = st.get ();
+    m_last_state_transition = st.get ();
+
+    m_epath_element.m_state_transition = std::move (st);
+  }
+
+private:
+  state_transition *&m_last_state_transition;
+  exploded_path::element_t &m_epath_element;
+};
+
+/* Populate the elements of EPATH with diagnostic_state and state_transition
+   information pertinent to the pending diagnostic.  */
+
+void
+diagnostic_manager::annotate_exploded_path (const path_builder &pb,
+                                           exploded_path &epath) const
+{
+  auto logger = get_logger ();
+  LOG_SCOPE (logger);
+
+  // TODO: consolidate this with build_emission_path?
+  interesting_t interest;
+  pb.get_pending_diagnostic ()->mark_interesting_stuff (&interest);
+
+  gcc_assert (epath.m_elements.size () > 0);
+
+  diagnostic_state curr_state;
+  state_transition *last_state_transition = nullptr;
+
+  if (interest.m_read_regions.size () > 0)
+    curr_state = interest.m_read_regions[0];
+
+  // Walk epath backwards, propagating annotation information
+  for (int idx = epath.m_elements.size () - 1; idx >= 0; --idx)
+    {
+      exploded_path::element_t &iter_element = epath.m_elements[idx];
+      if (logger)
+       {
+         logger->log ("edge[%i]: considering rewinding EN %i -> EN %i",
+                      idx,
+                      iter_element.m_eedge->m_src->m_index,
+                      iter_element.m_eedge->m_dest->m_index);
+         logger->start_log_line ();
+         logger->log_partial ("curr_state: ");
+         curr_state.dump_to_pp (logger->get_printer ());
+         logger->end_log_line ();
+       }
+      iter_element.m_state_at_dst = curr_state;
+      const exploded_edge *eedge = iter_element.m_eedge;
+      gcc_assert (eedge);
+
+      epath_rewind_context ctxt (*eedge, logger, curr_state,
+                                last_state_transition, iter_element);
+      if (eedge->m_custom_info)
+       {
+         if (logger)
+           {
+             logger->start_log_line ();
+             logger->log_partial ("custom_edge_info: ");
+             eedge->m_custom_info->print (logger->get_printer ());
+             logger->end_log_line ();
+           }
+         if (!eedge->m_custom_info->try_to_rewind_data_flow (ctxt))
+           {
+             if (logger)
+               logger->log ("could not rewind custom info");
+             return;
+           }
+       }
+      else if (const operation *op = eedge->maybe_get_op ())
+       {
+         if (logger)
+           {
+             logger->start_log_line ();
+             logger->log_partial ("op: ");
+             op->print_as_edge_label (logger->get_printer (), false);
+             logger->end_log_line ();
+           }
+         if (!op->try_to_rewind_data_flow (ctxt))
+           {
+             if (logger)
+               logger->log ("could not rewind op");
+             return;
+           }
+       }
+
+      iter_element.m_state_at_src = ctxt.m_output;
+      curr_state = ctxt.m_output;
+      if (logger)
+       {
+         logger->log ("rewound");
+         logger->start_log_line ();
+         logger->log_partial ("curr_state: ");
+         curr_state.dump_to_pp (logger->get_printer ());
+         logger->end_log_line ();
+       }
+    }
+}
+
 /* Emit a "path" of events to EMISSION_PATH describing the exploded path
    EPATH within EG.  */
 
@@ -1683,10 +1834,12 @@ diagnostic_manager::build_emission_path (const 
path_builder &pb,
   }
 
   /* Walk EPATH, adding events as appropriate.  */
-  for (unsigned i = 0; i < epath.m_edges.length (); i++)
+  for (unsigned i = 0; i < epath.m_elements.size (); ++i)
     {
-      const exploded_edge *eedge = epath.m_edges[i];
-      add_events_for_eedge (pb, *eedge, emission_path, &interest);
+      const exploded_edge *eedge = epath.m_elements[i].m_eedge;
+      gcc_assert (eedge);
+      add_events_for_eedge (pb, *eedge, emission_path, &interest,
+                           epath.m_elements[i].m_state_transition.get ());
     }
   add_event_on_final_node (pb, epath.get_final_enode (),
                           emission_path, &interest);
@@ -1895,7 +2048,8 @@ void
 diagnostic_manager::add_events_for_eedge (const path_builder &pb,
                                          const exploded_edge &eedge,
                                          checker_path *emission_path,
-                                         interesting_t *interest) const
+                                         interesting_t *interest,
+                                         const state_transition *state_trans) 
const
 {
   const exploded_node *src_node = eedge.m_src;
   const program_point &src_point = src_node->get_point ();
@@ -1913,11 +2067,19 @@ diagnostic_manager::add_events_for_eedge (const 
path_builder &pb,
       src_point.print (pp, format (false));
       pp_string (pp, "-> ");
       dst_point.print (pp, format (false));
+      if (state_trans)
+       {
+         pp_string (pp, " {");
+         state_trans->dump_to_pp (pp);
+         pp_string (pp, "}");
+       }
       get_logger ()->end_log_line ();
     }
   const program_state &src_state = src_node->get_state ();
   const program_state &dst_state = dst_node->get_state ();
 
+  bool created_event_for_state_trans = false;
+
   /* Add state change events for the states that have changed.
      We add these before events for superedges, so that if we have a
      state_change_event due to following an edge, we'll get this sequence
@@ -1946,11 +2108,15 @@ diagnostic_manager::add_events_for_eedge (const 
path_builder &pb,
   /* 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, *pd);
+    {
+      eedge.m_custom_info->add_events_to_path (emission_path, eedge, *pd,
+                                              state_trans);
+      created_event_for_state_trans = true;
+    }
 
   /* Don't add events for insignificant edges at verbosity levels below 3.  */
   if (m_verbosity < 3)
-    if (!significant_edge_p (pb, eedge))
+    if (!significant_edge_p (pb, eedge) && !state_trans)
       return;
 
   /* Add events for operations.  */
@@ -1962,7 +2128,11 @@ diagnostic_manager::add_events_for_eedge (const 
path_builder &pb,
   if (dst_point.get_supernode ()->entry_p ())
     {
       pb.get_pending_diagnostic ()->add_function_entry_event
-       (eedge, emission_path);
+       (eedge, emission_path,
+        (state_trans
+         ? state_trans->dyn_cast_state_transition_at_call ()
+         : nullptr));
+      created_event_for_state_trans = true;
       /* Create region_creation_events for on-stack regions within
         this frame.  */
       if (interest)
@@ -2028,6 +2198,14 @@ diagnostic_manager::add_events_for_eedge (const 
path_builder &pb,
        }
     }
 
+  /* If we have a state transition and haven't yet created an
+     event that describes it, do so now.  */
+  if (state_trans && !created_event_for_state_trans)
+    emission_path->add_event
+      (std::make_unique<state_transition_event>
+       (eedge.m_src->get_point (),
+       state_trans));
+
   if (pb.get_feasibility_problem ()
       && &pb.get_feasibility_problem ()->m_eedge == &eedge)
     {
@@ -2241,6 +2419,21 @@ diagnostic_manager::prune_for_sm_diagnostic 
(checker_path *path,
          /* Don't filter these.  */
          break;
 
+       case event_kind::state_transition:
+         /* Prune these if they have an empty description.  */
+         {
+           tree_dump_pretty_printer pp (nullptr);
+           base_event->print_desc (pp);
+           if (strlen (pp_formatted_text (&pp)) == 0)
+             {
+               log (("filtering event %i:"
+                     " state_transition_event with empty description"),
+                    idx);
+               path->delete_event (idx);
+             }
+         }
+         break;
+
        case event_kind::function_entry:
          if (m_verbosity < 1)
            {
diff --git a/gcc/analyzer/diagnostic-manager.h 
b/gcc/analyzer/diagnostic-manager.h
index 58c01664965..9f01a28dafd 100644
--- a/gcc/analyzer/diagnostic-manager.h
+++ b/gcc/analyzer/diagnostic-manager.h
@@ -101,7 +101,7 @@ public:
   }
 
   bool calc_best_epath (epath_finder *pf);
-  const exploded_path *get_best_epath () const { return m_best_epath.get (); }
+  exploded_path *get_best_epath () const { return m_best_epath.get (); }
   unsigned get_epath_length () const;
 
   void add_duplicate (saved_diagnostic *other);
@@ -201,6 +201,10 @@ private:
   const diagnostics::logical_locations::manager &
   get_logical_location_manager () const;
 
+  void
+  annotate_exploded_path (const path_builder &pb,
+                         exploded_path &epath) const;
+
   void build_emission_path (const path_builder &pb,
                            const exploded_path &epath,
                            checker_path *emission_path) const;
@@ -213,7 +217,8 @@ private:
   void add_events_for_eedge (const path_builder &pb,
                             const exploded_edge &eedge,
                             checker_path *emission_path,
-                            interesting_t *interest) const;
+                            interesting_t *interest,
+                            const state_transition *state_trans) const;
 
   bool significant_edge_p (const path_builder &pb,
                           const exploded_edge &eedge) const;
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 3e559b47a5d..f1a9ad4b785 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-predict.h"
 #include "context.h"
 #include "channels.h"
+#include "pretty-print-markup.h"
 
 #include "text-art/dump.h"
 
@@ -308,10 +309,10 @@ public:
               after the SSA name was set? (if any).  */
 
            for (unsigned idx = idx_of_def_stmt + 1;
-                idx < epath.m_edges.length ();
+                idx < epath.m_elements.size ();
                 ++idx)
              {
-               const exploded_edge *eedge = epath.m_edges[idx];
+               const exploded_edge *eedge = epath.m_elements[idx].m_eedge;
                if (logger)
                  logger->log ("eedge[%i]: EN %i -> EN %i",
                               idx,
@@ -352,10 +353,10 @@ public:
          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)
+       for (int idx = epath.m_elements.size () - 1; idx >= 0; --idx)
          {
+           const exploded_path::element_t &element = epath.m_elements[idx];
+           const exploded_edge *eedge = element.m_eedge;
            if (logger)
              {
                logger->log ("eedge[%i]: EN %i -> EN %i",
@@ -403,10 +404,10 @@ private:
   {
     LOG_SCOPE (logger);
 
-    int idx;
-    const exploded_edge *eedge;
-    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, idx, eedge)
+    for (int idx = epath.m_elements.size () - 1; idx >= 0; --idx)
       {
+       const exploded_path::element_t &element = epath.m_elements[idx];
+       const exploded_edge *eedge = element.m_eedge;
        if (eedge->m_src->get_stack_depth ()
            != eedge->m_dest->get_stack_depth ())
          {
@@ -1319,7 +1320,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &) const final override
+                          pending_diagnostic &,
+                          const state_transition *) const final override
   {
     const exploded_node *dst_node = eedge.m_dest;
     const program_point &dst_point = dst_node->get_point ();
@@ -1369,7 +1371,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &) const final override
+                          pending_diagnostic &,
+                          const state_transition *) const final override
   {
     const exploded_node *src_node = eedge.m_src;
     const program_point &src_point = src_node->get_point ();
@@ -1644,7 +1647,7 @@ 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);
+  pp_gimple_stmt_1 (pp, &get_gcall (), 0, (dump_flags_t)0);
 }
 
 void
@@ -1667,16 +1670,70 @@ 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);
+  model->update_for_gcall (get_gcall (), ctxt, &m_callee_fun);
   return true;
 }
 
 void
 interprocedural_call::add_events_to_path (checker_path *emission_path,
                                          const exploded_edge &eedge,
-                                         pending_diagnostic &pd) const
+                                         pending_diagnostic &pd,
+                                         const state_transition *state_trans) 
const
+{
+  pd.add_call_event (eedge, get_gcall (), *emission_path,
+                    (state_trans
+                     ? state_trans->dyn_cast_state_transition_at_call ()
+                     : nullptr));
+}
+
+bool
+interprocedural_call::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  auto logger = ctxt.m_logger;
+
+  // Rewind from params to arguments
+  if (ctxt.m_input.m_region_holding_value)
+    {
+      const region_model *dst_enode_model
+       = ctxt.m_eedge.m_dest->get_state ().m_region_model;
+      tree dst_tree
+       = dst_enode_model->get_representative_tree
+       (ctxt.m_input.m_region_holding_value);
+      if (dst_tree)
+       {
+         callsite_expr expr;
+         tree src_tree
+           = m_op.map_expr_from_callee_to_caller (m_callee_fun.decl,
+                                                  dst_tree,
+                                                  &expr);
+         if (src_tree)
+           {
+             const region_model *src_enode_model
+               = ctxt.m_eedge.m_src->get_state ().m_region_model;
+             ctxt.m_output.m_region_holding_value
+               = src_enode_model->get_lvalue (src_tree, nullptr);
+
+             ctxt.add_state_transition
+               (std::make_unique<state_transition_at_call> (expr));
+
+             if (logger)
+               {
+                 callsite_expr_element e (expr);
+                 logger->log ("updating m_region_holding_value from %qE to %qE"
+                              " (callsite_expr: %e)",
+                              dst_tree, src_tree, &e);
+               }
+           }
+       }
+    }
+
+  return true;
+}
+
+const gcall &
+interprocedural_call::get_gcall () const
 {
-  pd.add_call_event (eedge, m_call_stmt, *emission_path);
+  return m_op.get_gcall ();
 }
 
 // class interprocedural_return : public custom_edge_info
@@ -1715,7 +1772,8 @@ interprocedural_return::update_model (region_model *model,
 void
 interprocedural_return::add_events_to_path (checker_path *emission_path,
                                            const exploded_edge &eedge,
-                                           pending_diagnostic &) const
+                                           pending_diagnostic &,
+                                           const state_transition 
*state_trans) const
 {
   const program_point &dst_point = eedge.m_dest->get_point ();
   emission_path->add_event
@@ -1723,7 +1781,29 @@ interprocedural_return::add_events_to_path (checker_path 
*emission_path,
        (eedge,
        event_loc_info (m_call_stmt.location,
                        dst_point.get_fndecl (),
-                       dst_point.get_stack_depth ())));
+                       dst_point.get_stack_depth ()),
+       (state_trans
+        ? state_trans->dyn_cast_state_transition_at_return ()
+        : nullptr)));
+}
+
+bool
+interprocedural_return::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  auto logger = ctxt.m_logger;
+
+  tree lhs = gimple_call_lhs (&m_call_stmt);
+  if (!lhs)
+    return true;
+
+  const region_model *src_enode_model
+    = ctxt.m_eedge.m_src->get_state ().m_region_model;
+  tree fndecl = src_enode_model->get_current_function ()->decl;
+  tree fn_result = DECL_RESULT (fndecl);
+
+  ctxt.on_data_flow (DECL_RESULT (fndecl), lhs);
+
+  return true;
 }
 
 /* class exploded_edge : public dedge<eg_traits>.  */
@@ -2311,7 +2391,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &,
-                          pending_diagnostic &) const final override
+                          pending_diagnostic &,
+                          const state_transition *) const final override
   {
     emission_path->add_event
       (std::make_unique<tainted_args_function_custom_event>
@@ -2767,7 +2848,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &,
-                          pending_diagnostic &) const final override
+                          pending_diagnostic &,
+                          const state_transition *) const final override
   {
     /* Show the field in the struct declaration, e.g.
        "(1) field 'store' is marked with '__attribute__((tainted_args))'"  */
diff --git a/gcc/analyzer/event-loc-info.h b/gcc/analyzer/event-loc-info.h
index 24c2af10534..ace75fd9339 100644
--- a/gcc/analyzer/event-loc-info.h
+++ b/gcc/analyzer/event-loc-info.h
@@ -39,6 +39,10 @@ struct event_loc_info
   int m_depth;
 };
 
+extern event_loc_info
+event_loc_info_for_function_entry (const program_point &point,
+                                  const state_transition_at_call *state_trans);
+
 } // namespace ana
 
 #endif /* GCC_ANALYZER_EVENT_LOC_INFO_H */
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 0972e217baf..8fcc59a9ce0 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -381,9 +381,9 @@ private:
 class interprocedural_call : public custom_edge_info
 {
 public:
-  interprocedural_call (const gcall &call_stmt,
+  interprocedural_call (const call_and_return_op &op,
                        function &callee_fun)
-  : m_call_stmt (call_stmt),
+  : m_op (op),
     m_callee_fun (callee_fun)
   {}
 
@@ -402,10 +402,16 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &pd) const final override;
+                          pending_diagnostic &pd,
+                          const state_transition *state_trans) const final 
override;
+
+  bool
+  try_to_rewind_data_flow (rewind_context &) const final override;
+
+  const gcall &get_gcall () const;
 
 private:
-  const gcall &m_call_stmt;
+  const call_and_return_op &m_op;
   function &m_callee_fun;
 };
 
@@ -434,7 +440,11 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &pd) const final override;
+                          pending_diagnostic &pd,
+                          const state_transition *state_trans) const final 
override;
+
+  bool
+  try_to_rewind_data_flow (rewind_context &) const final override;
 
 private:
   const gcall &m_call_stmt;
@@ -463,7 +473,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge,
-                          pending_diagnostic &pd) const final override;
+                          pending_diagnostic &pd,
+                          const state_transition *state_trans) const final 
override;
 
   program_point
   get_point_before_setjmp () const
diff --git a/gcc/analyzer/exploded-path.cc b/gcc/analyzer/exploded-path.cc
index b10761a9388..4f35de272e2 100644
--- a/gcc/analyzer/exploded-path.cc
+++ b/gcc/analyzer/exploded-path.cc
@@ -26,19 +26,30 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
-/* class exploded_path.  */
+// struct diagnostic_state
 
-/* Copy ctor.  */
+void
+diagnostic_state::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s: {", m_debug_desc.c_str ());
+  if (m_region_holding_value)
+    {
+      pp_string (pp, "region holding value: ");
+      m_region_holding_value->dump_to_pp (pp, false);
+    }
+  pp_string (pp, "}");
+}
 
-exploded_path::exploded_path (const exploded_path &other)
-: m_edges (other.m_edges.length ())
+void
+diagnostic_state::dump () const
 {
-  int i;
-  const exploded_edge *eedge;
-  FOR_EACH_VEC_ELT (other.m_edges, i, eedge)
-    m_edges.quick_push (eedge);
+  tree_dump_pretty_printer pp (stderr);
+  dump_to_pp (&pp);
+  pp_newline (&pp);
 }
 
+/* class exploded_path.  */
+
 /* Look for the last use of SEARCH_STMT within this path.
    If found write the edge's index to *OUT_IDX and return true, otherwise
    return false.  */
@@ -47,31 +58,33 @@ bool
 exploded_path::find_stmt_backwards (const gimple *search_stmt,
                                    int *out_idx) const
 {
-  int i;
-  const exploded_edge *eedge;
-  FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
-    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)))
+  for (int i = m_elements.size () - 1; i >= 0; --i)
+    {
+      const element_t *element = &m_elements[i];
+      const exploded_edge *eedge = element->m_eedge;
+      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;
              }
-      }
-    else
-      {
-       /* Non-phi stmt.  */
-       if (const gimple *stmt = eedge->maybe_get_stmt ())
-         if (stmt == search_stmt)
-           {
-             *out_idx = i;
-             return true;
-           }
-      }
+       }
+    }
   return false;
 }
 
@@ -80,8 +93,8 @@ exploded_path::find_stmt_backwards (const gimple *search_stmt,
 exploded_node *
 exploded_path::get_final_enode () const
 {
-  gcc_assert (m_edges.length () > 0);
-  return m_edges[m_edges.length () - 1]->m_dest;
+  gcc_assert (m_elements.size () > 0);
+  return m_elements.back ().m_eedge->m_dest;
 }
 
 /* Check state along this path, returning true if it is feasible.
@@ -99,9 +112,9 @@ exploded_path::feasible_p (logger *logger,
                           eg->get_supergraph ());
 
   /* Traverse the path, updating this state.  */
-  for (unsigned edge_idx = 0; edge_idx < m_edges.length (); edge_idx++)
+  for (unsigned edge_idx = 0; edge_idx < m_elements.size (); ++edge_idx)
     {
-      const exploded_edge *eedge = m_edges[edge_idx];
+      const exploded_edge *eedge = m_elements[edge_idx].m_eedge;
       if (logger)
        logger->log ("considering edge %i: EN:%i -> EN:%i",
                     edge_idx,
@@ -140,13 +153,20 @@ void
 exploded_path::dump_to_pp (pretty_printer *pp,
                           const extrinsic_state *ext_state) const
 {
-  for (unsigned i = 0; i < m_edges.length (); i++)
+  for (unsigned i = 0; i < m_elements.size (); ++i)
     {
-      const exploded_edge *eedge = m_edges[i];
-      pp_printf (pp, "m_edges[%i]: EN %i -> EN %i",
+      const element_t &element = m_elements[i];
+      const exploded_edge *eedge = element.m_eedge;
+      pp_printf (pp, "m_elements[%i]: EN %i -> EN %i",
                 i,
                 eedge->m_src->m_index,
                 eedge->m_dest->m_index);
+      if (element.m_state_transition)
+       {
+         pp_string (pp, " {");
+         element.m_state_transition->dump_to_pp (pp);
+         pp_string (pp, "}");
+       }
       pp_newline (pp);
 
       if (ext_state)
@@ -188,6 +208,49 @@ exploded_path::dump_to_file (const char *filename,
   fclose (fp);
 }
 
+/* Print a multiline form of this path to LOGGER, prefixing it with DESC.  */
+
+void
+exploded_path::maybe_log (logger *logger, const char *desc) const
+{
+  if (!logger)
+    return;
+  logger->start_log_line ();
+  logger->log_partial ("%s: ", desc);
+  logger->end_log_line ();
+  for (unsigned idx = 0; idx < m_elements.size (); idx++)
+    {
+      const exploded_edge &eedge = *m_elements[idx].m_eedge;
+      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 ();
+
+      pretty_printer *pp = logger->get_printer ();
+      logger->start_log_line ();
+      pp_printf (pp, "  [%i] EN %i -> EN %i: ",
+                idx,
+                src_node->m_index,
+                dst_node->m_index);
+      src_point.print (pp, format (false));
+      pp_string (pp, " -> ");
+      dst_point.print (pp, format (false));
+      if (auto state_trans = m_elements[idx].m_state_transition.get ())
+       {
+         pp_string (pp, " {");
+         state_trans->dump_to_pp (pp);
+         pp_string (pp, "}");
+       }
+      logger->end_log_line ();
+    }
+}
+
+void
+exploded_path::reverse ()
+{
+  std::reverse (m_elements.begin (), m_elements.end ());
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/exploded-path.h b/gcc/analyzer/exploded-path.h
index 8c5ce9766a1..9b3ae1c3cbf 100644
--- a/gcc/analyzer/exploded-path.h
+++ b/gcc/analyzer/exploded-path.h
@@ -22,6 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_EXPLODED_PATH_H
 
 #include "analyzer/exploded-graph.h"
+#include "analyzer/checker-event.h"
+#include "analyzer/state-transition.h"
 
 namespace ana {
 
@@ -30,10 +32,45 @@ namespace ana {
 class exploded_path
 {
 public:
-  exploded_path () : m_edges () {}
-  exploded_path (const exploded_path &other);
-
-  unsigned length () const { return m_edges.length (); }
+  struct element_t
+  {
+    element_t (const exploded_edge *eedge)
+    : m_eedge (eedge)
+    {
+    }
+    element_t (const element_t &other)
+    : m_eedge (other.m_eedge),
+      m_state_at_src (other.m_state_at_src),
+      m_state_at_dst (other.m_state_at_dst),
+      m_state_transition (nullptr)
+    {
+      if (other.m_state_transition)
+       m_state_transition = other.m_state_transition->clone ();
+    }
+
+    element_t (element_t &&other) = default;
+
+    element_t &operator= (const element_t &other)
+    {
+      m_eedge = other.m_eedge;
+      m_state_at_src = other.m_state_at_src;
+      m_state_at_dst = other.m_state_at_dst;
+      m_state_transition = (other.m_state_transition
+                           ? other.m_state_transition->clone ()
+                           : nullptr);
+      return *this;
+    }
+
+    const exploded_edge *m_eedge;
+    diagnostic_state m_state_at_src;
+    diagnostic_state m_state_at_dst;
+    std::unique_ptr<state_transition> m_state_transition;
+  };
+
+  exploded_path () = default;
+  exploded_path (const exploded_path &other) = default;
+
+  unsigned length () const { return m_elements.size (); }
 
   bool find_stmt_backwards (const gimple *search_stmt,
                            int *out_idx) const;
@@ -47,10 +84,21 @@ public:
   void dump_to_file (const char *filename,
                     const extrinsic_state &ext_state) const;
 
+  void maybe_log (logger *logger, const char *desc) const;
+
   bool feasible_p (logger *logger, std::unique_ptr<feasibility_problem> *out,
                    engine *eng, const exploded_graph *eg) const;
 
-  auto_vec<const exploded_edge *> m_edges;
+  void
+  append_edge (const exploded_edge *edge)
+  {
+    m_elements.push_back (edge);
+  }
+
+  void
+  reverse ();
+
+  std::vector<element_t> m_elements;
 };
 
 /* Finding the shortest exploded_path within an exploded_graph.  */
diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc
index b3097f189a1..37ba84d012c 100644
--- a/gcc/analyzer/feasible-graph.cc
+++ b/gcc/analyzer/feasible-graph.cc
@@ -187,12 +187,12 @@ feasible_graph::make_epath (feasible_node *fnode) const
       gcc_assert (fnode->m_preds.length () == 1);
       feasible_edge *pred_fedge
        = static_cast <feasible_edge *> (fnode->m_preds[0]);
-      epath->m_edges.safe_push (pred_fedge->get_inner_edge ());
+      epath->m_elements.push_back (pred_fedge->get_inner_edge ());
       fnode = static_cast <feasible_node *> (pred_fedge->m_src);
     }
 
   /* Now reverse it.  */
-  epath->m_edges.reverse ();
+  epath->reverse ();
 
   return epath;
 }
diff --git a/gcc/analyzer/infinite-recursion.cc 
b/gcc/analyzer/infinite-recursion.cc
index c1dc6e49b9b..50da76c4a42 100644
--- a/gcc/analyzer/infinite-recursion.cc
+++ b/gcc/analyzer/infinite-recursion.cc
@@ -98,7 +98,8 @@ public:
 
   void
   add_function_entry_event (const exploded_edge &eedge,
-                           checker_path *emission_path) final override
+                           checker_path *emission_path,
+                           const state_transition_at_call *state_trans) final 
override
   {
     /* Subclass of function_entry_event for use when reporting both
        the initial and subsequent entries to the function of interest,
@@ -111,7 +112,9 @@ public:
                                      const program_state &dst_state,
                                      const infinite_recursion_diagnostic &pd,
                                      bool topmost)
-      : function_entry_event (dst_point, dst_state),
+      : function_entry_event (event_loc_info (dst_point),
+                             dst_state,
+                             nullptr),
        m_pd (pd),
        m_topmost (topmost)
       {
@@ -161,7 +164,8 @@ public:
        (std::make_unique<recursive_function_entry_event>
         (dst_point, dst_node->get_state (), *this, true));
     else
-      pending_diagnostic::add_function_entry_event (eedge, emission_path);
+      pending_diagnostic::add_function_entry_event (eedge, emission_path,
+                                                   state_trans);
   }
 
   /* Customize the location where the warning_event appears, putting
diff --git a/gcc/analyzer/ops.cc b/gcc/analyzer/ops.cc
index b40e078c928..549a2e00cce 100644
--- a/gcc/analyzer/ops.cc
+++ b/gcc/analyzer/ops.cc
@@ -38,6 +38,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/call-summary.h"
 #include "analyzer/call-info.h"
 #include "analyzer/analysis-plan.h"
+#include "analyzer/callsite-expr.h"
+#include "analyzer/state-transition.h"
 
 #if ENABLE_ANALYZER
 
@@ -66,6 +68,25 @@ event_loc_info::event_loc_info (const program_point &point)
   m_depth = point.get_stack_depth ();
 }
 
+/* Make an event_loc_info suitable for a function_entry_event at POINT.
+   If STATE_TRANS is non-null, then try to extract the pertinent parameter
+   from it and use the location of that parameter, rather than that of the
+   function name.  */
+
+event_loc_info
+event_loc_info_for_function_entry (const program_point &point,
+                                  const state_transition_at_call *state_trans)
+{
+  event_loc_info result (point);
+  if (state_trans)
+    {
+      callsite_expr expr = state_trans->get_callsite_expr ();
+      expr.maybe_get_param_location (point.get_fndecl (),
+                                    &result.m_loc);
+    }
+  return result;
+}
+
 // struct operation_context
 
 void
@@ -190,6 +211,56 @@ private:
   } m_path_context;
 };
 
+// struct rewind_context
+
+void
+rewind_context::on_data_origin (tree dst_tree)
+{
+  gcc_assert (dst_tree);
+  const region_model *dst_enode_model
+    = m_eedge.m_dest->get_state ().m_region_model;
+  const region *dst_reg_in_dst_enode
+    = dst_enode_model->get_lvalue (dst_tree, nullptr);
+  if (m_input.m_region_holding_value == dst_reg_in_dst_enode)
+    {
+      if (m_logger)
+       m_logger->log ("data origin, into %qE", dst_tree);
+      m_output.m_region_holding_value = nullptr;
+      add_state_transition
+       (std::make_unique<state_transition_origin> (dst_tree));
+    }
+}
+
+void
+rewind_context::on_data_flow (tree src_tree, tree dst_tree)
+{
+  gcc_assert (src_tree);
+  gcc_assert (dst_tree);
+  const region_model *dst_enode_model
+    = m_eedge.m_dest->get_state ().m_region_model;
+  const region *dst_reg_in_dst_enode
+    = dst_enode_model->get_lvalue (dst_tree, nullptr);
+  if (m_input.m_region_holding_value == dst_reg_in_dst_enode)
+    {
+      if (m_logger)
+       m_logger->log ("rewinding from %qE to %qE", dst_tree, src_tree);
+      const region_model *src_enode_model
+       = m_eedge.m_src->get_state ().m_region_model;
+      const region *src_reg_in_src_enode
+       = src_enode_model->get_lvalue (src_tree, nullptr);
+      m_output.m_region_holding_value = src_reg_in_src_enode;
+
+      if (TREE_CODE (src_tree) == RESULT_DECL)
+       add_state_transition (std::make_unique<state_transition_at_return> ());
+      else if (auto state_trans
+                = state_transition::make (m_output.m_region_holding_value,
+                                          src_tree,
+                                          m_input.m_region_holding_value,
+                                          dst_tree))
+       add_state_transition (std::move (state_trans));
+    }
+}
+
 // class gimple_stmt_op : public operation
 
 void
@@ -663,8 +734,58 @@ gimple_stmt_op::add_any_events_for_eedge (const 
exploded_edge &eedge,
     }
 }
 
+// class gasm_op : public gimple_stmt_op
+
 // class gassign_op : public gimple_stmt_op
 
+bool
+gassign_op::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  auto logger = ctxt.m_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 ();
+    }
+
+  const gassign &assign = get_gassign ();
+  tree lhs = gimple_assign_lhs (&assign);
+
+  if (!ctxt.could_be_affected_by_write_p (lhs))
+    return true;
+
+  tree rhs1 = gimple_assign_rhs1 (&assign);
+  enum tree_code op = gimple_assign_rhs_code (&assign);
+
+  switch (op)
+    {
+    default:
+      return false;
+
+    case NOP_EXPR:
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+    case COMPONENT_REF:
+      ctxt.on_data_flow (rhs1, lhs);
+      break;
+
+    case INTEGER_CST:
+    case REAL_CST:
+      if (logger)
+       logger->log ("value comes from here");
+      ctxt.on_data_origin (lhs);
+      break;
+    }
+
+  return true;
+}
+
+// class predict_op : public gimple_stmt_op
+
 // class greturn_op : public gimple_stmt_op
 
 void
@@ -734,6 +855,24 @@ greturn_op::add_any_events_for_eedge (const exploded_edge 
&,
   // No-op.
 }
 
+
+bool
+greturn_op::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  auto logger = ctxt.m_logger;
+  LOG_SCOPE (logger);
+
+  if (get_retval ())
+    {
+      const region_model *src_enode_model
+       = ctxt.m_eedge.m_src->get_state ().m_region_model;
+      tree fndecl = src_enode_model->get_current_function ()->decl;
+      ctxt.on_data_flow (get_retval (), DECL_RESULT (fndecl));
+    }
+
+  return true;
+}
+
 // class call_and_return_op : public gimple_stmt_op
 
 std::unique_ptr<operation>
@@ -839,7 +978,7 @@ call_and_return_op::execute (operation_context &op_ctxt) 
const
            const program_point dst_point
              (callee_entry_snode, *dst_call_string);
            auto edge_info
-             = std::make_unique<interprocedural_call> (get_gcall (),
+             = std::make_unique<interprocedural_call> (*this,
                                                        *callee_fun);
            edge_info->update_state (&dst_state, nullptr, &ctxt);
            op_ctxt.add_outcome (dst_point, dst_state, false, nullptr,
@@ -1023,6 +1162,13 @@ replay_call_summaries (operation_context &op_ctxt,
     }
 }
 
+bool
+call_and_return_op::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  LOG_SCOPE (ctxt.m_logger);
+  return true;
+}
+
 /* A concrete call_info subclass representing a replay of a call summary.  */
 
 class call_summary_edge_info : public call_info
@@ -2350,6 +2496,16 @@ phis_for_edge_op::add_any_events_for_eedge (const 
exploded_edge &,
   // No-op
 }
 
+bool
+phis_for_edge_op::try_to_rewind_data_flow (rewind_context &ctxt) const
+{
+  auto logger = ctxt.m_logger;
+  LOG_SCOPE (logger);
+  for (auto iter : m_pairs)
+    ctxt.on_data_flow (iter.m_src, iter.m_dst);
+  return true;
+}
+
 // class resx_op : public gimple_stmt_op
 
 void
diff --git a/gcc/analyzer/ops.h b/gcc/analyzer/ops.h
index f1c67d4e771..f8a09c0b819 100644
--- a/gcc/analyzer/ops.h
+++ b/gcc/analyzer/ops.h
@@ -74,6 +74,36 @@ struct operation_context
   const superedge &m_sedge;
 };
 
+struct rewind_context
+{
+  rewind_context (const exploded_edge &eedge,
+                 logger *logger,
+                 diagnostic_state input_state)
+  : m_eedge (eedge),
+    m_logger (logger),
+    m_input (input_state),
+    m_output (input_state)
+  {
+  }
+
+  void
+  on_data_origin (tree dst);
+
+  void
+  on_data_flow (tree src, tree dst);
+
+  virtual bool
+  could_be_affected_by_write_p (tree lhs) = 0;
+
+  virtual void
+  add_state_transition (std::unique_ptr<state_transition>) = 0;
+
+  const exploded_edge &m_eedge;
+  logger *m_logger;
+  diagnostic_state m_input;
+  diagnostic_state m_output;
+};
+
 /* Abstract base class for an operation along a superedge.  */
 
 class operation
@@ -163,6 +193,12 @@ class operation
 
   enum kind get_kind () const { return m_kind; }
 
+  virtual bool
+  try_to_rewind_data_flow (rewind_context &) const
+  {
+    return false;
+  }
+
 protected:
   operation (enum kind kind_)
   : m_kind (kind_)
@@ -279,6 +315,9 @@ public:
   {
     return *as_a <const gassign *> (&get_stmt ());
   }
+
+  bool
+  try_to_rewind_data_flow (rewind_context &ctxt) const final override;
 };
 
 /* An operation subclass for a GIMPLE_PREDICT stmt.
@@ -300,6 +339,12 @@ public:
   {
     return std::make_unique<predict_op> (get_stmt ());
   }
+
+  bool
+  try_to_rewind_data_flow (rewind_context &) const final override
+  {
+    return true;
+  }
 };
 
 /* An operation subclass representing both:
@@ -350,6 +395,9 @@ public:
   {
     return gimple_return_retval (&get_greturn ());
   }
+
+  bool
+  try_to_rewind_data_flow (rewind_context &ctxt) const final override;
 };
 
 /* A concrete operation subclass representing the effect of a GIMPLE_CALL stmt.
@@ -428,6 +476,9 @@ public:
   const known_function *
   maybe_get_known_function (const call_details &cd) const;
 
+  bool
+  try_to_rewind_data_flow (rewind_context &ctxt) const final override;
+
 private:
   cgraph_edge *
   get_any_cgraph_edge (operation_context &op_ctxt) const;
@@ -638,6 +689,12 @@ public:
 
   const gimple &get_ctrlflow_stmt () const { return m_ctrlflow_stmt; }
 
+  bool
+  try_to_rewind_data_flow (rewind_context &) const final override
+  {
+    return true;
+  }
+
 protected:
   control_flow_op (enum kind kind_,
                   ::edge cfg_edge,
@@ -983,6 +1040,9 @@ public:
   add_any_events_for_eedge (const exploded_edge &eedge,
                            checker_path &out_path) const final override;
 
+  bool
+  try_to_rewind_data_flow (rewind_context &ctxt) const final override;
+
   const std::vector<pair> &get_pairs () const { return m_pairs; }
 
 private:
diff --git a/gcc/analyzer/pending-diagnostic.cc 
b/gcc/analyzer/pending-diagnostic.cc
index 2d90a91766f..778b5baec7a 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -58,6 +58,15 @@ interesting_t::add_region_creation (const region *reg)
   m_region_creation.safe_push (reg);
 }
 
+/* Mark the value read from REG as being interesting.  */
+
+void
+interesting_t::add_read_region (const region *reg, std::string debug_desc)
+{
+  gcc_assert (reg);
+  m_read_regions.push_back (diagnostic_state (std::move (debug_desc), reg));
+}
+
 void
 interesting_t::dump_to_pp (pretty_printer *pp, bool simple) const
 {
@@ -70,6 +79,14 @@ interesting_t::dump_to_pp (pretty_printer *pp, bool simple) 
const
        pp_string (pp, ", ");
       reg->dump_to_pp (pp, simple);
     }
+  pp_string (pp, "], read regions: [");
+  for (i = 0; i < m_read_regions.size (); ++i)
+    {
+      auto &ann = m_read_regions[i];
+      if (i > 0)
+       pp_string (pp, ", ");
+      ann.dump_to_pp (pp);
+    }
   pp_string (pp, "]}");
 }
 
@@ -199,14 +216,21 @@ pending_diagnostic::fixup_location (location_t loc, bool) 
const
 
 void
 pending_diagnostic::add_function_entry_event (const exploded_edge &eedge,
-                                             checker_path *emission_path)
+                                             checker_path *emission_path,
+                                             const state_transition_at_call 
*state_trans)
 {
   const exploded_node *dst_node = eedge.m_dest;
   const program_point &dst_point = dst_node->get_point ();
   const program_state &dst_state = dst_node->get_state ();
+
+  /* If we have STATE_TRANS with a specific param, put the event on
+     that parameter, otherwise put in on the function name.  */
+  auto loc_info {event_loc_info_for_function_entry (dst_point, state_trans)};
+
   emission_path->add_event
-    (std::make_unique<function_entry_event> (dst_point,
-                                            dst_state));
+    (std::make_unique<function_entry_event> (loc_info,
+                                            dst_state,
+                                            state_trans));
 }
 
 /* Base implementation of pending_diagnostic::add_call_event.
@@ -215,11 +239,13 @@ pending_diagnostic::add_function_entry_event (const 
exploded_edge &eedge,
 void
 pending_diagnostic::add_call_event (const exploded_edge &eedge,
                                    const gcall &,
-                                   checker_path &emission_path)
+                                   checker_path &emission_path,
+                                   const state_transition_at_call *state_trans)
 {
   emission_path.add_event
     (std::make_unique<call_event> (eedge,
-                                  event_loc_info (eedge.m_src)));
+                                  event_loc_info (eedge.m_src),
+                                  state_trans));
 }
 
 /* Base implementation of pending_diagnostic::add_region_creation_events.
diff --git a/gcc/analyzer/pending-diagnostic.h 
b/gcc/analyzer/pending-diagnostic.h
index 4236acc780f..dccdb8a3f68 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -23,23 +23,31 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "diagnostics/metadata.h"
 #include "analyzer/sm.h"
+#include "analyzer/state-transition.h"
 
 namespace ana {
 
 /* A bundle of information about things that are of interest to a
-   pending_diagnostic.
+   pending_diagnostic:
 
-   For now, merely the set of regions that are pertinent to the
+   * a set of regions that are pertinent to the
    diagnostic, so that we can notify the user about when they
-   were created.  */
+   were created.
+
+   * a set of regions that a pertinent value for the diagnostic was
+   read from, so that we can notify the user about where those values
+   came from.  */
 
 struct interesting_t
 {
   void add_region_creation (const region *reg);
 
+  void add_read_region (const region *reg, std::string debug_desc);
+
   void dump_to_pp (pretty_printer *pp, bool simple) const;
 
   auto_vec<const region *> m_region_creation;
+  std::vector<diagnostic_state> m_read_regions;
 };
 
 /* Various bundles of information used for generating more precise
@@ -74,23 +82,42 @@ struct state_change
   const state_change_event &m_event;
 };
 
+/* For use by pending_diagnostic::describe_origin_of_state.  */
+
+struct origin_of_state
+{
+  origin_of_state (tree dst_reg_expr)
+  : m_dst_reg_expr (dst_reg_expr)
+  {
+    gcc_assert (m_dst_reg_expr);
+  }
+
+  tree m_dst_reg_expr;
+};
+
 /* For use by pending_diagnostic::describe_call_with_state.  */
 
 struct call_with_state
 {
   call_with_state (tree caller_fndecl, tree callee_fndecl,
-                  tree expr, state_machine::state_t state)
+                  tree expr, state_machine::state_t state,
+                  const state_transition_at_call *state_trans)
   : m_caller_fndecl (caller_fndecl),
     m_callee_fndecl (callee_fndecl),
     m_expr (expr),
-    m_state (state)
+    m_state (state),
+    m_state_trans (state_trans)
   {
+    if (state_trans)
+      m_src_event_id = state_trans->get_src_event_id ();
   }
 
   tree m_caller_fndecl;
   tree m_callee_fndecl;
   tree m_expr;
   state_machine::state_t m_state;
+  const state_transition_at_call *m_state_trans;
+  diagnostics::paths::event_id_t m_src_event_id;
 };
 
 /* For use by pending_diagnostic::describe_return_of_state.  */
@@ -98,16 +125,58 @@ struct call_with_state
 struct return_of_state
 {
   return_of_state (tree caller_fndecl, tree callee_fndecl,
-                  state_machine::state_t state)
+                  state_machine::state_t state,
+                  const state_transition_at_return *state_trans)
   : m_caller_fndecl (caller_fndecl),
     m_callee_fndecl (callee_fndecl),
-    m_state (state)
+    m_state (state),
+    m_state_trans (state_trans)
   {
+    if (state_trans)
+      m_src_event_id = state_trans->get_src_event_id ();
   }
 
   tree m_caller_fndecl;
   tree m_callee_fndecl;
   state_machine::state_t m_state;
+  const state_transition_at_return *m_state_trans;
+  diagnostics::paths::event_id_t m_src_event_id;
+};
+
+/* For use by pending_diagnostic::describe_copy_of_state.  */
+
+struct copy_of_state
+{
+  copy_of_state (tree src_reg_expr,
+                diagnostics::paths::event_id_t src_event_id,
+                tree dst_reg_expr)
+  : m_src_reg_expr (src_reg_expr),
+    m_src_event_id (src_event_id),
+    m_dst_reg_expr (dst_reg_expr)
+  {
+    gcc_assert (m_src_reg_expr);
+    gcc_assert (m_dst_reg_expr);
+  }
+
+  tree m_src_reg_expr;
+  diagnostics::paths::event_id_t m_src_event_id;
+  tree m_dst_reg_expr;
+};
+
+/* For use by pending_diagnostic::describe_use_of_state.  */
+
+struct use_of_state
+{
+  use_of_state (tree src_reg_expr,
+               diagnostics::paths::event_id_t src_event_id)
+  : m_src_reg_expr (src_reg_expr),
+    m_src_event_id (src_event_id)
+  {
+    gcc_assert (m_src_reg_expr);
+  }
+
+  tree m_src_reg_expr;
+  diagnostics::paths::event_id_t m_src_event_id;
 };
 
 /* For use by pending_diagnostic::describe_final_event.  */
@@ -268,6 +337,20 @@ class pending_diagnostic
     return diagnostics::paths::event::meaning ();
   }
 
+  /* Precision-of-wording vfunc for use in describing state_transition_event
+     instances of state_transition::kind::origin.
+     Return true if a description of the event was printed to the
+     pretty-printer, or false otherwise.
+     For example, a divide-by-zero diagnostic might use:
+       "zero value originates here"
+     at the point where the zero comes from.  */
+  virtual bool describe_origin_of_state (pretty_printer &,
+                                        const evdesc::origin_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return false;
+  }
+
   /* Precision-of-wording vfunc for describing an interprocedural call
      carrying critial state for the diagnostic, from caller to callee.
 
@@ -299,6 +382,32 @@ class pending_diagnostic
     return false;
   }
 
+  /* Precision-of-wording vfunc for use in describing state_transition_event
+     instances of state_transition::kind::copy.
+     Return true if a description of the event was printed to the
+     pretty-printer, or false otherwise.
+     For example, a divide-by-zero diagnostic might use:
+     "copying zero value from (3) from 'x' to 'y'".  */
+  virtual bool describe_copy_of_state (pretty_printer &,
+                                      const evdesc::copy_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return false;
+  }
+
+  /* Precision-of-wording vfunc for use in describing state_transition_event
+     instances of state_transition::kind::use.
+     Return true if a description of the event was printed to the
+     pretty-printer, or false otherwise.
+     For example, a divide-by-zero diagnostic might use:
+     "using zero value from (7) from 'y'".  */
+  virtual bool describe_use_of_state (pretty_printer &,
+                                     const evdesc::use_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return false;
+  }
+
   /* Precision-of-wording vfunc for describing the final event within a
      diagnostic path.
 
@@ -322,7 +431,8 @@ class pending_diagnostic
 
   virtual void
   add_function_entry_event (const exploded_edge &eedge,
-                           checker_path *emission_path);
+                           checker_path *emission_path,
+                           const state_transition_at_call *state_trans);
 
   /* Vfunc for extending/overriding creation of the events for an
      exploded_edge, allowing for custom events to be created that are
@@ -343,7 +453,8 @@ class pending_diagnostic
      the variadic arguments.  */
   virtual void add_call_event (const exploded_edge &,
                               const gcall &call_stmt,
-                              checker_path &emission_path);
+                              checker_path &emission_path,
+                              const state_transition_at_call *state_trans);
 
   /* Vfunc for adding any events for the creation of regions identified
      by the mark_interesting_stuff vfunc.
diff --git a/gcc/analyzer/poisoned-value-diagnostic.cc 
b/gcc/analyzer/poisoned-value-diagnostic.cc
index d29ceeaca63..e5d5cec7a35 100644
--- a/gcc/analyzer/poisoned-value-diagnostic.cc
+++ b/gcc/analyzer/poisoned-value-diagnostic.cc
@@ -155,7 +155,10 @@ public:
   void mark_interesting_stuff (interesting_t *interest) final override
   {
     if (m_src_region)
-      interest->add_region_creation (m_src_region);
+      {
+       interest->add_region_creation (m_src_region);
+       interest->add_read_region (m_src_region, "poisoned value");
+      }
   }
 
   /* Attempt to suppress false positives.
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index e4bafebbaa4..9555f72c307 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -69,6 +69,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/feasible-graph.h"
 #include "analyzer/record-layout.h"
 #include "analyzer/function-set.h"
+#include "analyzer/state-transition.h"
 
 #if ENABLE_ANALYZER
 
@@ -855,12 +856,57 @@ private:
   const region *m_base_reg_b;
 };
 
+/* Locate the parameter with the given index within FNDECL.
+   ARGNUM is zero based, -1 indicates the `this' argument of a method.
+   Return the location of the FNDECL itself if there are problems.  */
+
+bool
+callsite_expr::maybe_get_param_location (tree fndecl,
+                                        location_t *out_loc) const
+{
+  gcc_assert (fndecl);
+
+  if (DECL_ARTIFICIAL (fndecl))
+    return false;
+
+  tree param = get_param_tree (fndecl);
+  if (!param)
+    return false;
+
+  *out_loc = DECL_SOURCE_LOCATION (param);
+  return true;
+}
+
+/* If this callsite_expr refers to a parameter, get the PARM_DECL from
+   FNDECL.
+   Return NULL_TREE on any problems.  */
+
+tree
+callsite_expr::get_param_tree (tree fndecl) const
+{
+  if (!param_p ())
+    return NULL_TREE;
+
+  int i;
+  tree param;
+
+  /* Locate param by index within DECL_ARGUMENTS (fndecl).  */
+  for (i = 1, param = DECL_ARGUMENTS (fndecl);
+       i < param_num () && param;
+       i++, param = TREE_CHAIN (param))
+    ;
+
+  return param;
+}
+
 class div_by_zero_diagnostic
 : public pending_diagnostic_subclass<div_by_zero_diagnostic>
 {
 public:
-  div_by_zero_diagnostic (const gassign *assign)
-  : m_assign (assign)
+  div_by_zero_diagnostic (const gassign *assign,
+                         const region *divisor_reg)
+  : m_assign (assign),
+    m_divisor_reg (divisor_reg)
   {}
 
   const char *get_kind () const final override
@@ -891,8 +937,142 @@ public:
     return true;
   }
 
+  void
+  mark_interesting_stuff (interesting_t *interest)
+  {
+    interest->add_read_region (m_divisor_reg, "divisor zero value");
+  }
+
+  void
+  add_function_entry_event (const exploded_edge &eedge,
+                           checker_path *emission_path,
+                           const state_transition_at_call *state_trans)
+  {
+    class custom_function_entry_event : public function_entry_event
+    {
+    public:
+      custom_function_entry_event (const event_loc_info &loc_info,
+                                  const program_state &state,
+                                  const state_transition_at_call *state_trans)
+      : function_entry_event (loc_info,
+                             state,
+                             state_trans)
+      {
+      }
+
+      void print_desc (pretty_printer &pp) const override
+      {
+       if (auto state_trans = get_state_transition_at_call ())
+         {
+           auto expr = state_trans->get_callsite_expr ();
+           if (tree parm = expr.get_param_tree (m_effective_fndecl))
+             {
+               auto src_event_id = state_trans->get_src_event_id ();
+               if (src_event_id.known_p ())
+                 pp_printf (&pp, "entry to %qE with zero from %@ for %qE",
+                            m_effective_fndecl,
+                            &src_event_id,
+                            parm);
+               else
+                 pp_printf (&pp, "entry to %qE with zero for %qE",
+                            m_effective_fndecl, parm);
+               return;
+             }
+         }
+       return function_entry_event::print_desc (pp);
+      }
+    };
+
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    const program_state &dst_state = dst_node->get_state ();
+    auto loc_info {event_loc_info_for_function_entry (dst_point, state_trans)};
+    emission_path->add_event
+      (std::make_unique<custom_function_entry_event> (loc_info,
+                                                     dst_state,
+                                                     state_trans));
+  }
+
+  bool
+  describe_origin_of_state (pretty_printer &pp,
+                           const evdesc::origin_of_state &) final override
+  {
+    pp_printf (&pp, "zero value originates here");
+    return true;
+  }
+
+  bool
+  describe_call_with_state (pretty_printer &pp,
+                           const evdesc::call_with_state &evd) final override
+  {
+    if (evd.m_state_trans)
+      {
+       callsite_expr expr = evd.m_state_trans->get_callsite_expr ();
+       if (expr.param_p ())
+         {
+           if (evd.m_src_event_id.known_p ())
+             pp_printf (&pp, "passing zero from %@ from %qE to %qE via 
parameter %i",
+                        &evd.m_src_event_id,
+                        evd.m_caller_fndecl,
+                        evd.m_callee_fndecl,
+                        expr.param_num ());
+           else
+             pp_printf (&pp, "passing zero from %qE to %qE via parameter %i",
+                        evd.m_caller_fndecl,
+                        evd.m_callee_fndecl,
+                        expr.param_num ());
+           return true;
+         }
+      }
+
+    return false;
+  }
+
+  bool
+  describe_return_of_state (pretty_printer &pp,
+                           const evdesc::return_of_state &evd) final override
+  {
+    if (evd.m_src_event_id.known_p ())
+      pp_printf (&pp, "returning zero from %@ from %qE here",
+                &evd.m_src_event_id,
+                evd.m_callee_fndecl);
+    else
+      pp_printf (&pp, "returning zero from %qE here",
+              evd.m_callee_fndecl);
+    return true;
+  }
+
+  bool
+  describe_copy_of_state (pretty_printer &pp,
+                         const evdesc::copy_of_state &evd) final override
+  {
+    if (evd.m_src_event_id.known_p ())
+      pp_printf (&pp, "copying zero value from %@ from %qE to %qE",
+                &evd.m_src_event_id,
+                evd.m_src_reg_expr, evd.m_dst_reg_expr);
+    else
+      pp_printf (&pp, "copying zero value from %qE to %qE",
+                evd.m_src_reg_expr, evd.m_dst_reg_expr);
+    return true;
+  }
+
+  bool
+  describe_use_of_state (pretty_printer &pp,
+                        const evdesc::use_of_state &evd) final override
+  {
+    if (evd.m_src_event_id.known_p ())
+      pp_printf (&pp, "using zero value from %@ from %qE",
+                &evd.m_src_event_id,
+                evd.m_src_reg_expr);
+    else
+      pp_printf (&pp, "using zero value from %qE",
+                evd.m_src_reg_expr);
+    return true;
+  }
+
 private:
   const gassign *m_assign;
+  const region *m_divisor_reg;
 };
 
 /* Check the pointer subtraction SVAL_A - SVAL_B at ASSIGN and add
@@ -1101,15 +1281,26 @@ region_model::get_gassign_result (const gassign *assign,
                  && INTEGRAL_TYPE_P (TREE_TYPE (rhs1)))
                {
                  if (tree_int_cst_sgn (rhs2_cst) < 0)
-                   ctxt->warn
-                     (make_shift_count_negative_diagnostic (assign, rhs2_cst));
+                   {
+                     const region *rhs2_reg
+                       = get_lvalue (gimple_assign_rhs2 (assign), nullptr);
+                     ctxt->warn
+                       (make_shift_count_negative_diagnostic (assign,
+                                                              rhs2_cst,
+                                                              rhs2_reg));
+                   }
                  else if (compare_tree_int (rhs2_cst,
                                             TYPE_PRECISION (TREE_TYPE (rhs1)))
                           >= 0)
-                   ctxt->warn (make_shift_count_overflow_diagnostic
-                               (assign,
-                                int (TYPE_PRECISION (TREE_TYPE (rhs1))),
-                                rhs2_cst));
+                   {
+                     const region *rhs2_reg
+                       = get_lvalue (gimple_assign_rhs2 (assign), nullptr);
+                     ctxt->warn (make_shift_count_overflow_diagnostic
+                                 (assign,
+                                  int (TYPE_PRECISION (TREE_TYPE (rhs1))),
+                                  rhs2_cst,
+                                  rhs2_reg));
+                   }
                }
          }
 
@@ -1130,8 +1321,11 @@ region_model::get_gassign_result (const gassign *assign,
                {
                  if (ctxt)
                    {
+                     const region *rhs2_reg
+                       = get_lvalue (gimple_assign_rhs2 (assign), nullptr);
                      ctxt->warn
-                       (std::make_unique<div_by_zero_diagnostic> (assign));
+                       (std::make_unique<div_by_zero_diagnostic> (assign,
+                                                                  rhs2_reg));
                      ctxt->terminate_path ();
                    }
                  return nullptr;
@@ -1935,7 +2129,8 @@ public:
   void
   add_events_to_path (checker_path *emission_path,
                      const exploded_edge &eedge,
-                     pending_diagnostic &) const final override
+                     pending_diagnostic &,
+                     const state_transition *) const final override
   {
     const exploded_node *dst_node = eedge.m_dest;
     const program_point &dst_point = dst_node->get_point ();
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 4b0160a98b0..fd947da65e8 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -1338,12 +1338,14 @@ make_poisoned_value_diagnostic (tree expr, enum 
poison_kind pkind,
 
 extern std::unique_ptr<pending_diagnostic>
 make_shift_count_negative_diagnostic (const gassign *assign,
-                                     tree count_cst);
+                                     tree count_cst,
+                                     const region *src_region);
 
 extern std::unique_ptr<pending_diagnostic>
 make_shift_count_overflow_diagnostic (const gassign *assign,
                                      int operand_precision,
-                                     tree count_cst);
+                                     tree count_cst,
+                                     const region *src_region);
 
 extern std::unique_ptr<pending_diagnostic>
 make_write_to_const_diagnostic (const region *dest_reg, tree decl);
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index 5c1d980017a..c852f7f51af 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -22,7 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_REGION_H
 
 #include "analyzer/symbol.h"
-#include "text-art/widget.h"
+#include "analyzer/store.h"
+#include "text-art/tree-widget.h"
 
 namespace ana {
 
diff --git a/gcc/analyzer/setjmp-longjmp.cc b/gcc/analyzer/setjmp-longjmp.cc
index 711dfb3e8d0..7fe0033095e 100644
--- a/gcc/analyzer/setjmp-longjmp.cc
+++ b/gcc/analyzer/setjmp-longjmp.cc
@@ -469,7 +469,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,
-                                  pending_diagnostic &) const
+                                  pending_diagnostic &,
+                                  const state_transition *) const
 {
   const exploded_node *src_node = eedge.m_src;
   const program_point &src_point = src_node->get_point ();
diff --git a/gcc/analyzer/shift-diagnostics.cc 
b/gcc/analyzer/shift-diagnostics.cc
index 2bc58de248d..724c1a18d64 100644
--- a/gcc/analyzer/shift-diagnostics.cc
+++ b/gcc/analyzer/shift-diagnostics.cc
@@ -35,8 +35,9 @@ class shift_count_negative_diagnostic
 : public pending_diagnostic_subclass<shift_count_negative_diagnostic>
 {
 public:
-  shift_count_negative_diagnostic (const gassign *assign, tree count_cst)
-  : m_assign (assign), m_count_cst (count_cst)
+  shift_count_negative_diagnostic (const gassign *assign, tree count_cst,
+                                  const region *src_region)
+  : m_assign (assign), m_count_cst (count_cst), m_src_region (src_region)
   {}
 
   const char *get_kind () const final override
@@ -70,15 +71,24 @@ public:
     return true;
   }
 
+  void
+  mark_interesting_stuff (interesting_t *interest)
+  {
+    interest->add_read_region (m_src_region, "shift count value");
+  }
+
 private:
   const gassign *m_assign;
   tree m_count_cst;
+  const region *m_src_region;
 };
 
 std::unique_ptr<pending_diagnostic>
-make_shift_count_negative_diagnostic (const gassign *assign, tree count_cst)
+make_shift_count_negative_diagnostic (const gassign *assign, tree count_cst,
+                                     const region *src_region)
 {
-  return std::make_unique<shift_count_negative_diagnostic> (assign, count_cst);
+  return std::make_unique<shift_count_negative_diagnostic>
+    (assign, count_cst, src_region);
 }
 
 /* A subclass of pending_diagnostic for complaining about shifts
@@ -90,9 +100,11 @@ class shift_count_overflow_diagnostic
 public:
   shift_count_overflow_diagnostic (const gassign *assign,
                                   int operand_precision,
-                                  tree count_cst)
+                                  tree count_cst,
+                                  const region *src_region)
   : m_assign (assign), m_operand_precision (operand_precision),
-    m_count_cst (count_cst)
+    m_count_cst (count_cst),
+    m_src_region (src_region)
   {}
 
   const char *get_kind () const final override
@@ -128,19 +140,27 @@ public:
     return true;
   }
 
+  void
+  mark_interesting_stuff (interesting_t *interest)
+  {
+    interest->add_read_region (m_src_region, "shift count value");
+  }
+
 private:
   const gassign *m_assign;
   int m_operand_precision;
   tree m_count_cst;
+  const region *m_src_region;
 };
 
 std::unique_ptr<pending_diagnostic>
 make_shift_count_overflow_diagnostic (const gassign *assign,
                                      int operand_precision,
-                                     tree count_cst)
+                                     tree count_cst,
+                                     const region *src_region)
 {
   return std::make_unique<shift_count_overflow_diagnostic>
-    (assign, operand_precision, count_cst);
+    (assign, operand_precision, count_cst, src_region);
 }
 
 } // namespace ana
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index a348d36665f..08a0a2ad5eb 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -221,7 +221,8 @@ public:
 
   void add_events_to_path (checker_path *emission_path,
                           const exploded_edge &eedge ATTRIBUTE_UNUSED,
-                          pending_diagnostic &)
+                          pending_diagnostic &,
+                          const state_transition *)
     const final override
   {
     emission_path->add_event
diff --git a/gcc/analyzer/state-transition.cc b/gcc/analyzer/state-transition.cc
new file mode 100644
index 00000000000..6c1d2837a0f
--- /dev/null
+++ b/gcc/analyzer/state-transition.cc
@@ -0,0 +1,184 @@
+/* Classes for tracking pertinent events that happen along
+   an execution path.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+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 "tree-diagnostic.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"
+#include "analyzer/callsite-expr.h"
+#include "analyzer/state-transition.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+// class state_transition
+
+DEBUG_FUNCTION void
+state_transition::dump () const
+{
+  tree_dump_pretty_printer pp (stderr);
+  dump_to_pp (&pp);
+  pp_newline (&pp);
+}
+
+std::unique_ptr<state_transition>
+state_transition::make (const region *src_reg,
+                       tree src_reg_expr,
+                       const region *dst_reg,
+                       tree dst_reg_expr)
+{
+  gcc_assert (src_reg != dst_reg);
+  gcc_assert (dst_reg);
+
+  if (!src_reg)
+    return std::make_unique<state_transition_origin> (dst_reg_expr);
+
+  if (src_reg->get_parent_region () ==  dst_reg->get_parent_region ())
+    if (tree src_decl =  src_reg->maybe_get_decl ())
+      if (tree dst_decl =  dst_reg->maybe_get_decl ())
+       {
+         if (TREE_CODE (src_decl) == SSA_NAME
+             && TREE_CODE (dst_decl) == SSA_NAME
+             && SSA_NAME_VAR (src_decl)
+             && SSA_NAME_VAR (src_decl) == SSA_NAME_VAR (dst_decl))
+           {
+             /* Avoid printing "copying value from 'y' to 'y'.  */
+             return nullptr;
+           }
+       }
+
+  if (printable_expr_p (src_reg_expr))
+    {
+      if (printable_expr_p (dst_reg_expr))
+       return std::make_unique<state_transition_copy> (src_reg_expr,
+                                                       dst_reg_expr);
+      else
+       return std::make_unique<state_transition_use> (src_reg_expr);
+    }
+  else
+    return nullptr;
+}
+
+diagnostics::paths::event_id_t
+state_transition::get_src_event_id () const
+{
+  if (!m_prev_state_transition)
+    return diagnostics::paths::event_id_t ();
+  return m_prev_state_transition->m_event_id;
+}
+// class state_transition_origin : public state_transition
+
+std::unique_ptr<state_transition>
+state_transition_origin::clone () const
+{
+  return std::make_unique<state_transition_origin> (m_dst_reg_expr);
+}
+
+void
+state_transition_origin::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "state_transition_origin (dst: %qE)",
+            m_dst_reg_expr);
+}
+
+// class state_transition_at_call : public state_transition
+
+std::unique_ptr<state_transition>
+state_transition_at_call::clone () const
+{
+  return std::make_unique<state_transition_at_call> (m_expr);
+}
+
+void
+state_transition_at_call::dump_to_pp (pretty_printer *pp) const
+{
+  callsite_expr_element e (m_expr);
+  pp_printf (pp, "state_transition_at_call (callsite_expr: %e)", &e);
+}
+
+// class state_transition_at_return : public state_transition
+
+std::unique_ptr<state_transition>
+state_transition_at_return::clone () const
+{
+  return std::make_unique<state_transition_at_return> ();
+}
+
+void
+state_transition_at_return::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "state_transition_at_return");
+}
+
+// class state_transition_copy : public state_transition
+
+std::unique_ptr<state_transition>
+state_transition_copy::clone () const
+{
+  return std::make_unique<state_transition_copy> (m_src_reg_expr,
+                                                 m_dst_reg_expr);
+}
+
+void
+state_transition_copy::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "state_transition_copy (src: %qE, dst: %qE)",
+            m_src_reg_expr,
+            m_dst_reg_expr);
+}
+
+// class state_transition_use : public state_transition
+
+std::unique_ptr<state_transition>
+state_transition_use::clone () const
+{
+  return std::make_unique<state_transition_use> (m_src_reg_expr);
+}
+
+void
+state_transition_use::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "state_transition_use (src: %qE)",
+            m_src_reg_expr);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/state-transition.h b/gcc/analyzer/state-transition.h
new file mode 100644
index 00000000000..4615be7a839
--- /dev/null
+++ b/gcc/analyzer/state-transition.h
@@ -0,0 +1,191 @@
+/* Classes for tracking pertinent events that happen along
+   an execution path.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+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_STATE_TRANSITION_H
+#define GCC_ANALYZER_STATE_TRANSITION_H
+
+#include "diagnostics/event-id.h"
+#include "analyzer/callsite-expr.h"
+
+namespace ana {
+
+class state_transition
+{
+public:
+  enum class kind
+  {
+    origin,
+    at_call,
+    at_return,
+    copy,
+    use
+  };
+
+  state_transition ()
+  : m_prev_state_transition (nullptr)
+  {
+  }
+
+  virtual ~state_transition () {}
+
+  virtual std::unique_ptr<state_transition>
+  clone () const = 0;
+
+  virtual void
+  dump_to_pp (pretty_printer *pp) const = 0;
+
+  virtual enum kind get_kind () const = 0;
+
+  virtual const state_transition_at_call *
+  dyn_cast_state_transition_at_call () const { return nullptr; }
+
+  virtual const state_transition_at_return *
+  dyn_cast_state_transition_at_return () const { return nullptr; }
+
+  void dump () const;
+
+  static std::unique_ptr<state_transition>
+  make (const region *src_reg,
+       tree src_reg_expr,
+       const region *dst_reg,
+       tree dst_reg_expr);
+
+  diagnostics::paths::event_id_t
+  get_src_event_id () const;
+
+  state_transition *m_prev_state_transition;
+  diagnostics::paths::event_id_t m_event_id;
+};
+
+class state_transition_origin : public state_transition
+{
+public:
+  state_transition_origin (tree dst_reg_expr)
+  : m_dst_reg_expr (dst_reg_expr)
+  {
+  }
+
+  std::unique_ptr<state_transition>
+  clone () const final override;
+
+  void
+  dump_to_pp (pretty_printer *pp) const final override;
+
+  enum kind
+  get_kind () const final override { return kind::origin; }
+
+  tree m_dst_reg_expr;
+};
+
+class state_transition_at_call : public state_transition
+{
+public:
+  state_transition_at_call (callsite_expr expr)
+  : m_expr (expr)
+  {
+  }
+
+  std::unique_ptr<state_transition>
+  clone () const final override;
+
+  void
+  dump_to_pp (pretty_printer *pp) const final override;
+
+  enum kind
+  get_kind () const final override { return kind::at_call; }
+
+  const state_transition_at_call *
+  dyn_cast_state_transition_at_call () const final override { return this; }
+
+  callsite_expr
+  get_callsite_expr () const { return m_expr; }
+
+private:
+  callsite_expr m_expr;
+};
+
+class state_transition_at_return : public state_transition
+{
+public:
+  std::unique_ptr<state_transition>
+  clone () const final override;
+
+  void
+  dump_to_pp (pretty_printer *pp) const final override;
+
+  enum kind
+  get_kind () const final override { return kind::at_return; }
+
+  const state_transition_at_return *
+  dyn_cast_state_transition_at_return () const final override { return this; }
+};
+
+class state_transition_copy : public state_transition
+{
+public:
+  state_transition_copy (tree src_reg_expr,
+                        tree dst_reg_expr)
+  : m_src_reg_expr (src_reg_expr),
+    m_dst_reg_expr (dst_reg_expr)
+  {
+    gcc_assert (m_src_reg_expr);
+    gcc_assert (printable_expr_p (m_src_reg_expr));
+
+    gcc_assert (m_dst_reg_expr);
+    gcc_assert (printable_expr_p (m_dst_reg_expr));
+  }
+
+  std::unique_ptr<state_transition>
+  clone () const final override;
+
+  void
+  dump_to_pp (pretty_printer *pp) const final override;
+
+  enum kind
+  get_kind () const final override { return kind::copy; }
+
+  tree m_src_reg_expr;
+  tree m_dst_reg_expr;
+};
+
+class state_transition_use : public state_transition
+{
+public:
+  state_transition_use (tree src_reg_expr)
+  : m_src_reg_expr (src_reg_expr)
+  {
+  }
+
+  std::unique_ptr<state_transition>
+  clone () const final override;
+
+  void
+  dump_to_pp (pretty_printer *pp) const final override;
+
+  enum kind
+  get_kind () const final override { return kind::use; }
+
+  tree m_src_reg_expr;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_STATE_TRANSITION_H */
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index 5f3c9684ff0..c90dedbaff5 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -323,40 +323,6 @@ private:
   ::edge m_cfg_edge;
 };
 
-/* An ID representing an expression at a callsite:
-   either a parameter index, or the return value (or unknown).  */
-
-class callsite_expr
-{
- public:
-  callsite_expr () : m_val (-1) {}
-
-  static callsite_expr from_zero_based_param (int idx)
-  {
-    return callsite_expr (idx + 1);
-  }
-
-  static callsite_expr from_return_value ()
-  {
-    return callsite_expr (0);
-  }
-
-  bool param_p () const
-  {
-    return m_val > 0;
-  }
-
-  bool return_value_p () const
-  {
-    return m_val == 0;
-  }
-
- private:
-  callsite_expr (int val) : m_val (val) {}
-
-  int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown".  */
-};
-
 /* Base class for adding additional content to the .dot output
    for a supergraph.  */
 
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index 1a1d3565d55..d2dd8934c61 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -775,7 +775,8 @@ public:
      adding a custom call_event subclass.  */
   void add_call_event (const exploded_edge &eedge,
                       const gcall &call_stmt,
-                      checker_path &emission_path) override
+                      checker_path &emission_path,
+                      const state_transition_at_call *state_trans) override
   {
     /* As per call_event, but show the number of variadic arguments
        in the call.  */
@@ -785,7 +786,7 @@ public:
       va_arg_call_event (const exploded_edge &eedge,
                         const event_loc_info &loc_info,
                         int num_variadic_arguments)
-      : call_event (eedge, loc_info),
+      : call_event (eedge, loc_info, nullptr),
        m_num_variadic_arguments (num_variadic_arguments)
       {
       }
@@ -819,7 +820,8 @@ public:
            num_variadic_arguments));
       }
     else
-      pending_diagnostic::add_call_event (eedge, call_stmt, emission_path);
+      pending_diagnostic::add_call_event (eedge, call_stmt, emission_path,
+                                         state_trans);
   }
 
 protected:
diff --git a/gcc/digraph.cc b/gcc/digraph.cc
index 724d541e68f..fc015b8330b 100644
--- a/gcc/digraph.cc
+++ b/gcc/digraph.cc
@@ -96,6 +96,9 @@ struct test_cluster : public cluster<test_graph_traits>
 
 struct test_path
 {
+  void append_edge (const test_edge *edge) { m_edges.safe_push (edge); }
+  void reverse () { m_edges.reverse (); }
+
   auto_vec<const test_edge *> m_edges;
 };
 
diff --git a/gcc/shortest-paths.h b/gcc/shortest-paths.h
index dd0d4d2a193..5351b7e3737 100644
--- a/gcc/shortest-paths.h
+++ b/gcc/shortest-paths.h
@@ -187,7 +187,7 @@ get_shortest_path (const node_t *other_node) const
 
   while (m_best_edge[other_node->m_index])
     {
-      result.m_edges.safe_push (m_best_edge[other_node->m_index]);
+      result.append_edge (m_best_edge[other_node->m_index]);
       if (m_sense == SPS_FROM_GIVEN_ORIGIN)
        other_node = m_best_edge[other_node->m_index]->m_src;
       else
@@ -195,7 +195,7 @@ get_shortest_path (const node_t *other_node) const
     }
 
   if (m_sense == SPS_FROM_GIVEN_ORIGIN)
-    result.m_edges.reverse ();
+    result.reverse ();
 
   return result;
 }
diff --git a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c 
b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
index 0d0b8e01157..ff4f788f8d7 100644
--- a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
+++ b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
@@ -3,7 +3,7 @@
 static int __attribute__((noipa))
 return_zero (void)
 {
-  return 0;
+  return 0; /* { dg-message "value originates here" } */
 }
 
 void
diff --git a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-2.c 
b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-2.c
new file mode 100644
index 00000000000..75a25a669a6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-2.c
@@ -0,0 +1,14 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+extern int
+get_value (void);
+
+int
+test (int flag)
+{
+  int x = 42;
+  int y = 0; /* { dg-message "value originates here" } */
+  if (flag)
+    y = get_value ();
+  return x / y; /* { dg-warning "division by zero" } */
+}
diff --git a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-3.c 
b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-3.c
new file mode 100644
index 00000000000..ecbef931111
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-3.c
@@ -0,0 +1,19 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+extern int
+get_value (void);
+
+int
+test (int flag, int flag_2, int flag_3)
+{
+  int x = 42; /* { dg-bogus "value originates here" } */
+  int y = 10; /* { dg-bogus "value originates here" } */
+  int z = 0; /* { dg-message "value originates here" } */
+  if (flag)
+    y = get_value ();
+  if (flag_2)
+    z = get_value ();
+  if (flag_3)
+    y = z;
+  return x / y; /* { dg-warning "division by zero" } */
+}
diff --git a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c 
b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c
index 08e52728748..f8a3ed67383 100644
--- a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c
+++ b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-1.c
@@ -27,8 +27,10 @@ f2 (void)
   f1 (_dl_hwcaps_subdirs_build_bitmask (33, 31));
 }
 
-static int __attribute__((noinline)) op3 (int op, int c) { return op << c; } 
/* { dg-message "shift by negative count \\('-1'\\)" } */
-int test_3 (void) { return op3 (1, -1); }
+static int __attribute__((noinline)) op3 (int op, int c) { return op << c; } 
/* { dg-message "55: entry to 'op3' with problematic value for 'c'" } */
+/* { dg-message "shift by negative count \\('-1'\\)" "" { target *-*-* } .-1 } 
*/
+
+int test_3 (void) { return op3 (1, -1); } /* { dg-message "passing problematic 
value from 'test_3' to 'op3' via parameter 2" } */
 
 static int __attribute__((noinline)) op4 (int op, int c) { return op << c; }
 int test_4 (void) { return op4 (1, 0); }
diff --git a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-2.c 
b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-2.c
new file mode 100644
index 00000000000..19f8c4da407
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-2.c
@@ -0,0 +1,11 @@
+unsigned char
+do_shift (unsigned char val, int bits) /* { dg-message "34: entry to 
'do_shift' with problematic value for 'bits'" } */
+{
+  return val << bits; /* { dg-warning "Wanalyzer-shift-count-overflow" } */
+}
+
+int
+test (unsigned char ch)
+{
+  return do_shift (ch, 1000); /* { dg-message "passing problematic value from 
'test' to 'do_shift' via parameter 2" } */
+}
diff --git a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-3.c 
b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-3.c
new file mode 100644
index 00000000000..b0435a8b087
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-3.c
@@ -0,0 +1,12 @@
+unsigned char
+do_shift (unsigned char val, int bits) /* { dg-message "34: entry to 
'do_shift' with problematic value from \\\(2\\\) for 'bits'" } */
+{
+  return val << bits; /* { dg-warning "Wanalyzer-shift-count-overflow" } */
+}
+
+int
+test (unsigned char ch)
+{
+  int bits = 1000; /* { dg-message "\\\(2\\\) value originates here" } */
+  return do_shift (ch, bits); /* { dg-message "\\\(3\\\) passing problematic 
value from \\\(2\\\) from 'test' to 'do_shift' via parameter 2" } */
+}
diff --git a/gcc/testsuite/c-c++-common/analyzer/invalid-shift-4.c 
b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-4.c
new file mode 100644
index 00000000000..3dcfedbe95d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/invalid-shift-4.c
@@ -0,0 +1,12 @@
+unsigned char
+do_shift (unsigned char val, int bits) /* { dg-message "34: entry to 
'do_shift' with problematic value from \\\(2\\\) for 'bits'" } */
+{
+  return val << bits; /* { dg-warning "Wanalyzer-shift-count-negative" } */
+}
+
+int
+test (unsigned char ch)
+{
+  int bits = -1; /* { dg-message "\\\(2\\\) value originates here" } */
+  return do_shift (ch, bits); /* { dg-message "\\\(3\\\) passing problematic 
value from \\\(2\\\) from 'test' to 'do_shift' via parameter 2" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/divide-by-zero-7.C 
b/gcc/testsuite/g++.dg/analyzer/divide-by-zero-7.C
new file mode 100644
index 00000000000..3196a4f5731
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/divide-by-zero-7.C
@@ -0,0 +1,28 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+// TODO: we shouldn't need this:
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+struct foo
+{
+  foo (int x_, int y_)
+  : x (x_), y (y_) // TODO: should show event here
+  {
+  }
+
+  int divide () const
+  {
+    return x / y; /* { dg-message "using zero value from '\\*this\\.foo::y'" } 
*/
+    /* { dg-warning "division by zero" "" { target *-*-* } .-1 } */
+  }
+
+  int x;
+  int y;
+};
+
+int
+test ()
+{
+  foo f (5, 0); // TODO: should show "origin of zero" event here
+  return f.divide ();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-4.c 
b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-4.c
new file mode 100644
index 00000000000..b685ba1d91b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-4.c
@@ -0,0 +1,39 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+/* TODO: we shouldn't need this:  */
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+int
+get_zero (void)
+{
+  return 0; /* { dg-message "\\\(6\\\) zero value originates here" } */
+}
+
+struct foo { int x; int y; };
+
+void
+init_foo (struct foo *f, int x, int y) /* { dg-message "\\\(9\\\) entry to 
'init_foo' with zero from \\\(7\\\) for 'y'" } */
+{
+  f->x = x;
+  f->y = y; /* { dg-message "\\\(10\\\) copying zero value from \\\(9\\\) from 
'y' to '\\*f\\.y'" } */
+}
+
+int
+do_divide (struct foo *f)
+{
+  return f->x / f->y; /* { dg-message "using zero value from \\\(10\\\) from 
'\\*f\\.y'" } */
+  /* { dg-warning "division by zero" "" { target *-*-* } .-1 } */
+}
+
+int
+test (int flag, int flag_2, int flag_3)
+{
+  struct foo f;
+  int a = 42;
+  int b = 10;
+  if (flag)
+    b = get_zero ();
+    /* { dg-message "\\\(7\\\) returning zero from \\\(6\\\) from 'get_zero' 
here" "" { target *-*-* } .-1 } */
+  init_foo (&f, a, b); /* { dg-message "passing zero from \\\(7\\\) from 
'test' to 'init_foo' via parameter 3" } */
+  return do_divide (&f);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-5.c 
b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-5.c
new file mode 100644
index 00000000000..96c9d01700d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-5.c
@@ -0,0 +1,42 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+/* TODO: we shouldn't need this:  */
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+int
+maybe_get_zero (int flag)
+{
+  if (flag)
+    return 0; /* { dg-message "zero value originates here" } */
+  else
+    return 42;
+}
+
+struct foo { int x; int y; };
+
+void
+init_foo (struct foo *f, int x, int y) /* { dg-message "\\\(11\\\) entry to 
'init_foo' with zero from \\\(9\\\) for 'y'" } */
+{
+  f->x = x;
+  f->y = y; /* { dg-message "\\\(12\\\) copying zero value from \\\(11\\\) 
from 'y' to '\\*f\\.y'" } */
+}
+
+int
+do_divide (struct foo *f)
+{
+  return f->x / f->y; /* { dg-message "using zero value from \\\(12\\\) from 
'\\*f\\.y'" } */
+  /* { dg-warning "division by zero" "" { target *-*-* } .-1 } */
+}
+
+int
+test (int flag, int flag_2, int flag_3)
+{
+  struct foo f;
+  int a = 42;
+  int b = 10;
+  if (flag)
+    b = maybe_get_zero (flag_2); /* { dg-bogus "value of 'b' unchanged here" } 
*/
+    /* { dg-message "returning zero from \\\(8\\\) from 'maybe_get_zero' here" 
"" { target *-*-* } .-1 } */
+  init_foo (&f, a, b); /* { dg-message "passing zero from \\\(9\\\) from 
'test' to 'init_foo' via parameter 3" } */
+  return do_divide (&f);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-6.c 
b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-6.c
new file mode 100644
index 00000000000..5fd8539f109
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-6.c
@@ -0,0 +1,27 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+/* TODO: we shouldn't need this:  */
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+struct foo { int x; int y; };
+
+void
+init_foo (struct foo *f)
+{
+  __builtin_memset (f, 0, sizeof (f));
+}
+
+int
+do_divide (struct foo *f)
+{
+  return f->x / f->y; /* { dg-message "using zero value from '\\*f\\.y'" } */
+  /* { dg-warning "division by zero" "" { target *-*-* } .-1 } */
+}
+
+int
+test (int flag, int flag_2, int flag_3)
+{
+  struct foo f;
+  init_foo (&f);
+  return do_divide (&f);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c 
b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c
index 3aaee568bc1..940c5725169 100644
--- a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c
@@ -7,7 +7,7 @@ test_1 ()
 static float __attribute__((noinline))
 get_zero ()
 {
-  return 0.f;
+  return 0.f; /* { dg-message "value originates here" } */
 }
 
 float
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index d844e752c4b..1556c1de595 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -71,7 +71,8 @@ public:
   }
   ~tree_dump_pretty_printer ()
   {
-    pp_flush (this);
+    if (pp_buffer (this)->m_stream)
+      pp_flush (this);
   }
 };
 
-- 
2.49.0

Reply via email to