| /* Handling for the various __analyzer_* known functions. |
| Copyright (C) 2020-2025 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "analyzer/common.h" |
| |
| #include "diagnostic.h" |
| #include "tree-diagnostic.h" /* for default_tree_printer. */ |
| #include "pretty-print-markup.h" |
| |
| #include "analyzer/analyzer-logging.h" |
| #include "analyzer/region-model.h" |
| #include "analyzer/pending-diagnostic.h" |
| #include "analyzer/call-details.h" |
| #include "analyzer/program-state.h" |
| |
| #if ENABLE_ANALYZER |
| |
| namespace ana { |
| |
| /* Handle calls to "__analyzer_break" by triggering a breakpoint within |
| the analyzer. */ |
| |
| class kf_analyzer_break : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| void impl_call_pre (const call_details &) const final override |
| { |
| /* TODO: is there a good cross-platform way to do this? */ |
| raise (SIGINT); |
| } |
| }; |
| |
| /* Handler for calls to "__analyzer_describe". |
| |
| Emit a warning describing the 2nd argument (which can be of any |
| type), at the given verbosity level. This is for use when |
| debugging, and may be of use in DejaGnu tests. */ |
| |
| class kf_analyzer_describe : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 2; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| if (!cd.get_ctxt ()) |
| return; |
| tree t_verbosity = cd.get_arg_tree (0); |
| const svalue *sval = cd.get_arg_svalue (1); |
| bool simple = zerop (t_verbosity); |
| label_text desc = sval->get_desc (simple); |
| warning_at (cd.get_location (), 0, "svalue: %qs", desc.get ()); |
| } |
| }; |
| |
| /* Handler for calls to "__analyzer_dump_capacity". |
| |
| Emit a warning describing the capacity of the base region of |
| the region pointed to by the 1st argument. |
| This is for use when debugging, and may be of use in DejaGnu tests. */ |
| |
| class kf_analyzer_dump_capacity : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == 1 |
| && cd.arg_is_pointer_p (0)); |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| region_model *model = cd.get_model (); |
| tree t_ptr = cd.get_arg_tree (0); |
| const svalue *sval_ptr = model->get_rvalue (t_ptr, ctxt); |
| const region *reg = model->deref_rvalue (sval_ptr, t_ptr, ctxt); |
| const region *base_reg = reg->get_base_region (); |
| const svalue *capacity = model->get_capacity (base_reg); |
| label_text desc = capacity->get_desc (true); |
| warning_at (cd.get_call_stmt ().location, 0, |
| "capacity: %qs", desc.get ()); |
| } |
| }; |
| |
| /* Compare D1 and D2 using their names, and then IDs to order them. */ |
| |
| static int |
| cmp_decls (tree d1, tree d2) |
| { |
| gcc_assert (DECL_P (d1)); |
| gcc_assert (DECL_P (d2)); |
| if (DECL_NAME (d1) && DECL_NAME (d2)) |
| if (int cmp = strcmp (IDENTIFIER_POINTER (DECL_NAME (d1)), |
| IDENTIFIER_POINTER (DECL_NAME (d2)))) |
| return cmp; |
| return (int)DECL_UID (d1) - (int)DECL_UID (d2); |
| } |
| |
| /* Comparator for use by vec<tree>::qsort, |
| using their names, and then IDs to order them. */ |
| |
| static int |
| cmp_decls_ptr_ptr (const void *p1, const void *p2) |
| { |
| tree const *d1 = (tree const *)p1; |
| tree const *d2 = (tree const *)p2; |
| |
| return cmp_decls (*d1, *d2); |
| } |
| |
| /* Handler for calls to "__analyzer_dump_escaped". |
| |
| Emit a warning giving the number of decls that have escaped, followed |
| by a comma-separated list of their names, in alphabetical order. |
| |
| This is for use when debugging, and may be of use in DejaGnu tests. */ |
| |
| class kf_analyzer_dump_escaped : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| region_model *model = cd.get_model (); |
| |
| auto_vec<tree> escaped_decls; |
| for (auto iter : *model->get_store ()) |
| { |
| const binding_cluster *c = iter.second; |
| if (!c->escaped_p ()) |
| continue; |
| if (tree decl = c->get_base_region ()->maybe_get_decl ()) |
| escaped_decls.safe_push (decl); |
| } |
| |
| /* Sort them into deterministic order; alphabetical is |
| probably most user-friendly. */ |
| escaped_decls.qsort (cmp_decls_ptr_ptr); |
| |
| class escaped_list_element : public pp_element |
| { |
| public: |
| escaped_list_element (auto_vec<tree> &escaped_decls) |
| : m_escaped_decls (escaped_decls) |
| { |
| } |
| |
| void add_to_phase_2 (pp_markup::context &ctxt) final override |
| { |
| /* We can't call pp_printf directly on ctxt.m_pp from within |
| formatting. As a workaround, work with a clone of the pp. */ |
| std::unique_ptr<pretty_printer> pp (ctxt.m_pp.clone ()); |
| bool first = true; |
| for (auto iter : m_escaped_decls) |
| { |
| if (first) |
| first = false; |
| else |
| pp_string (pp.get (), ", "); |
| pp_printf (pp.get (), "%qD", iter); |
| } |
| pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ())); |
| } |
| |
| private: |
| auto_vec<tree> &m_escaped_decls; |
| } e_escaped (escaped_decls); |
| |
| /* Print the number to make it easier to write DejaGnu tests for |
| the "nothing has escaped" case. */ |
| warning_at (cd.get_location (), 0, "escaped: %i: %e", |
| escaped_decls.length (), |
| &e_escaped); |
| } |
| }; |
| |
| /* Placeholder handler for calls to "__analyzer_dump_exploded_nodes". |
| This is a no-op; the real implementation happens when the |
| exploded_graph is postprocessed. */ |
| |
| class kf_analyzer_dump_exploded_nodes : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 1; |
| } |
| }; |
| |
| /* Handler for calls to "__analyzer_dump_named_constant". |
| |
| Look up the given name, and emit a warning describing the |
| state of the corresponding stashed value. |
| |
| This is for use when debugging, and for DejaGnu tests. */ |
| |
| class kf_analyzer_dump_named_constant : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 1; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| |
| const char *name = cd.get_arg_string_literal (0); |
| if (!name) |
| { |
| error_at (cd.get_location (), "cannot determine name"); |
| return; |
| } |
| tree value = get_stashed_constant_by_name (name); |
| if (value) |
| warning_at (cd.get_location (), 0, "named constant %qs has value %qE", |
| name, value); |
| else |
| warning_at (cd.get_location (), 0, "named constant %qs has unknown value", |
| name); |
| } |
| }; |
| |
| /* A pending_diagnostic subclass for implementing "__analyzer_dump_path". */ |
| |
| class dump_path_diagnostic |
| : public pending_diagnostic_subclass<dump_path_diagnostic> |
| { |
| public: |
| dump_path_diagnostic (const program_state &state) |
| : m_state (state) |
| { |
| } |
| |
| int get_controlling_option () const final override |
| { |
| return 0; |
| } |
| |
| bool emit (diagnostic_emission_context &ctxt) final override |
| { |
| ctxt.inform ("path"); |
| return true; |
| } |
| |
| const char *get_kind () const final override |
| { |
| return "dump_path_diagnostic"; |
| } |
| |
| bool operator== (const dump_path_diagnostic &) const |
| { |
| return true; |
| } |
| |
| const program_state * |
| get_final_state () const final override |
| { |
| return &m_state; |
| } |
| |
| private: |
| program_state m_state; |
| }; |
| |
| /* Handle calls to "__analyzer_dump_path" by queuing a diagnostic at this |
| exploded_node. */ |
| |
| class kf_analyzer_dump_path : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| if (const program_state *state = ctxt->get_state ()) |
| ctxt->warn (std::make_unique<dump_path_diagnostic> (*state)); |
| } |
| }; |
| |
| /* Handle calls to "__analyzer_dump_region_model" by dumping |
| the region model's state to stderr. */ |
| |
| class kf_analyzer_dump_region_model : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| region_model *model = cd.get_model (); |
| model->dump (false); |
| } |
| }; |
| |
| /* Handle a call to "__analyzer_eval" by evaluating the input |
| and dumping as a dummy warning, so that test cases can use |
| dg-warning to validate the result (and so unexpected warnings will |
| lead to DejaGnu failures). |
| Broken out as a subroutine to make it easier to put a breakpoint on it |
| - though typically this doesn't help, as we have an SSA name as the arg, |
| and what's more interesting is usually the def stmt for that name. */ |
| |
| class kf_analyzer_eval : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 1; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| if (!ctxt) |
| return; |
| region_model *model = cd.get_model (); |
| |
| tree t_arg = cd.get_arg_tree (0); |
| tristate t = model->eval_condition (t_arg, NE_EXPR, integer_zero_node, |
| ctxt); |
| warning_at (cd.get_location (), 0, "%s", t.as_string ()); |
| } |
| }; |
| |
| /* Handler for "__analyzer_get_unknown_ptr". */ |
| |
| class kf_analyzer_get_unknown_ptr : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *ptr_sval |
| = mgr->get_or_create_unknown_svalue (cd.get_lhs_type ()); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| }; |
| |
| /* Populate KFM with instances of known functions used for debugging the |
| analyzer and for writing DejaGnu tests, all with a "__analyzer_" prefix. */ |
| |
| void |
| register_known_analyzer_functions (known_function_manager &kfm) |
| { |
| kfm.add ("__analyzer_break", |
| std::make_unique<kf_analyzer_break> ()); |
| kfm.add ("__analyzer_describe", |
| std::make_unique<kf_analyzer_describe> ()); |
| kfm.add ("__analyzer_dump_capacity", |
| std::make_unique<kf_analyzer_dump_capacity> ()); |
| kfm.add ("__analyzer_dump_escaped", |
| std::make_unique<kf_analyzer_dump_escaped> ()); |
| kfm.add ("__analyzer_dump_exploded_nodes", |
| std::make_unique<kf_analyzer_dump_exploded_nodes> ()); |
| kfm.add ("__analyzer_dump_named_constant", |
| std::make_unique<kf_analyzer_dump_named_constant> ()); |
| kfm.add ("__analyzer_dump_path", |
| std::make_unique<kf_analyzer_dump_path> ()); |
| kfm.add ("__analyzer_dump_region_model", |
| std::make_unique<kf_analyzer_dump_region_model> ()); |
| kfm.add ("__analyzer_eval", |
| std::make_unique<kf_analyzer_eval> ()); |
| kfm.add ("__analyzer_get_unknown_ptr", |
| std::make_unique<kf_analyzer_get_unknown_ptr> ()); |
| kfm.add ("__analyzer_get_strlen", |
| make_kf_strlen ()); |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |