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
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.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>