From: David Malcolm Date: Wed, 6 May 2026 13:57:07 +0000 (-0400) Subject: analyzer: add data flow events X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9a231dbb68ab3df13ea20a6066d45eaf7cc5a4ee;p=thirdparty%2Fgcc.git analyzer: add data flow events 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 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 --- 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 (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 . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_ANALYZER_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 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 (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 (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 (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 (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 (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 (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 (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 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 + (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 (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. */ @@ -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 @@ -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 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 *out, engine *eng, const exploded_graph *eg) const; - auto_vec m_edges; + void + append_edge (const exploded_edge *edge) + { + m_elements.push_back (edge); + } + + void + reverse (); + + std::vector 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 (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 (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 (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 (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 ()); + 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 @@ -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 (get_gcall (), + = std::make_unique (*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) = 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 (&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 (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 &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 (dst_point, - dst_state)); + (std::make_unique (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 (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 m_region_creation; + std::vector 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 { 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 (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 (assign)); + (std::make_unique (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 make_shift_count_negative_diagnostic (const gassign *assign, - tree count_cst); + tree count_cst, + const region *src_region); extern std::unique_ptr 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 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 { 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 -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 (assign, count_cst); + return std::make_unique + (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 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 - (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 . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "analyzer/common.h" + +#include "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::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 (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 (src_reg_expr, + dst_reg_expr); + else + return std::make_unique (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_origin::clone () const +{ + return std::make_unique (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_at_call::clone () const +{ + return std::make_unique (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_at_return::clone () const +{ + return std::make_unique (); +} + +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_copy::clone () const +{ + return std::make_unique (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_use::clone () const +{ + return std::make_unique (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 . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_ANALYZER_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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 struct test_path { + void append_edge (const test_edge *edge) { m_edges.safe_push (edge); } + void reverse () { m_edges.reverse (); } + auto_vec 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); } };