| /* m2rte.cc a plugin to detect runtime exceptions at compiletime. |
| |
| Copyright (C) 2017-2026 Free Software Foundation, Inc. |
| Contributed by Gaius Mulley <gaius@glam.ac.uk>. |
| |
| This file is part of GNU Modula-2. |
| |
| GNU Modula-2 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. |
| |
| GNU Modula-2 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 GNU Modula-2; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| |
| #include "gcc-plugin.h" |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "options.h" |
| #include "tree-pass.h" |
| #include "diagnostic-core.h" |
| #include "flags.h" |
| #include "intl.h" |
| #include "plugin.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "gimplify.h" |
| #include "gimple-iterator.h" |
| #include "gimplify-me.h" |
| #include "gimple-pretty-print.h" |
| #include "plugin-version.h" |
| #include "diagnostic.h" |
| #include "context.h" |
| |
| #include "rtegraph.h" |
| extern bool ggc_force_collect; |
| extern void ggc_collect (void); |
| |
| #undef DEBUG_BASICBLOCK |
| |
| int plugin_is_GPL_compatible; |
| |
| void debug_tree (tree); |
| |
| /* All dialects of Modula-2 issue some or all of these runtime error calls. |
| This plugin detects whether a runtime error will be called in the first |
| basic block of a reachable function. */ |
| |
| static const char *m2_runtime_error_calls[] = { |
| "m2pim_M2RTS_AssignmentException", |
| "m2pim_M2RTS_ReturnException", |
| "m2pim_M2RTS_IncException", |
| "m2pim_M2RTS_DecException", |
| "m2pim_M2RTS_InclException", |
| "m2pim_M2RTS_ExclException", |
| "m2pim_M2RTS_ShiftException", |
| "m2pim_M2RTS_RotateException", |
| "m2pim_M2RTS_StaticArraySubscriptException", |
| "m2pim_M2RTS_DynamicArraySubscriptException", |
| "m2pim_M2RTS_ForLoopBeginException", |
| "m2pim_M2RTS_ForLoopToException", |
| "m2pim_M2RTS_ForLoopEndException", |
| "m2pim_M2RTS_PointerNilException", |
| "m2pim_M2RTS_NoReturnException", |
| "m2pim_M2RTS_CaseException", |
| "m2pim_M2RTS_WholeNonPosDivException", |
| "m2pim_M2RTS_WholeNonPosModException", |
| "m2pim_M2RTS_WholeZeroDivException", |
| "m2pim_M2RTS_WholeZeroRemException", |
| "m2pim_M2RTS_WholeValueException", |
| "m2pim_M2RTS_RealValueException", |
| "m2pim_M2RTS_ParameterException", |
| "m2pim_M2RTS_NoException", |
| |
| "m2iso_M2RTS_AssignmentException", |
| "m2iso_M2RTS_ReturnException", |
| "m2iso_M2RTS_IncException", |
| "m2iso_M2RTS_DecException", |
| "m2iso_M2RTS_InclException", |
| "m2iso_M2RTS_ExclException", |
| "m2iso_M2RTS_ShiftException", |
| "m2iso_M2RTS_RotateException", |
| "m2iso_M2RTS_StaticArraySubscriptException", |
| "m2iso_M2RTS_DynamicArraySubscriptException", |
| "m2iso_M2RTS_ForLoopBeginException", |
| "m2iso_M2RTS_ForLoopToException", |
| "m2iso_M2RTS_ForLoopEndException", |
| "m2iso_M2RTS_PointerNilException", |
| "m2iso_M2RTS_NoReturnException", |
| "m2iso_M2RTS_CaseException", |
| "m2iso_M2RTS_WholeNonPosDivException", |
| "m2iso_M2RTS_WholeNonPosModException", |
| "m2iso_M2RTS_WholeZeroDivException", |
| "m2iso_M2RTS_WholeZeroRemException", |
| "m2iso_M2RTS_WholeValueException", |
| "m2iso_M2RTS_RealValueException", |
| "m2iso_M2RTS_ParameterException", |
| "m2iso_M2RTS_NoException", |
| NULL, |
| }; |
| |
| |
| #if defined(DEBUG_BASICBLOCK) |
| /* pretty_function display the name of the function. */ |
| |
| static void |
| pretty_function (tree fndecl) |
| { |
| if (fndecl != NULL && (DECL_NAME (fndecl) != NULL)) |
| { |
| const char *n = IDENTIFIER_POINTER (DECL_NAME (fndecl)); |
| fprintf (stderr, "PROCEDURE %s ;\n", n); |
| } |
| } |
| #endif |
| |
| void |
| print_rtl (FILE *outf, const_rtx rtx_first); |
| |
| /* strend returns true if string name has ending. */ |
| |
| static bool |
| strend (const char *name, const char *ending) |
| { |
| unsigned int len = strlen (name); |
| return (len > strlen (ending) |
| && (strcmp (&name[len-strlen (ending)], ending) == 0)); |
| } |
| |
| /* is_constructor returns true if the function name is that of a module |
| constructor or deconstructor. */ |
| |
| static bool |
| is_constructor (tree fndecl) |
| { |
| const char *name = IDENTIFIER_POINTER (DECL_NAME (fndecl)); |
| unsigned int len = strlen (name); |
| |
| return ((len > strlen ("_M2_")) |
| && (strncmp (name, "_M2_", strlen ("_M2_")) == 0) |
| && (strend (name, "_init") || strend (name, "_finish"))); |
| } |
| |
| /* is_external returns true if the function is extern. */ |
| |
| static bool |
| is_external (tree function) |
| { |
| return (! DECL_EXTERNAL (function)) |
| && TREE_PUBLIC (function) |
| && TREE_STATIC (function); |
| } |
| |
| /* is_external returns true if the function is a call to a Modula-2 |
| runtime exception handler. */ |
| |
| static bool |
| is_rte (tree fndecl) |
| { |
| const char *n = IDENTIFIER_POINTER (DECL_NAME (fndecl)); |
| |
| for (int i = 0; m2_runtime_error_calls[i] != NULL; i++) |
| if (strcmp (m2_runtime_error_calls[i], n) == 0) |
| return true; |
| return false; |
| } |
| |
| /* examine_call extract the function tree from the gimple call |
| statement and check whether it is a call to a runtime exception. */ |
| |
| static void |
| examine_call (gimple *stmt) |
| { |
| tree fndecl = gimple_call_fndecl (stmt); |
| rtenode *func = rtegraph_lookup (stmt, fndecl, true); |
| // rtegraph_dump (); |
| if (fndecl != NULL && (DECL_NAME (fndecl) != NULL)) |
| { |
| /* Firstly check if the function is a runtime exception. */ |
| if (is_rte (fndecl)) |
| { |
| /* Remember runtime exception call. */ |
| rtegraph_include_rtscall (func); |
| /* Add the callee to the list of candidates to be queried reachable. */ |
| rtegraph_candidates_include (func); |
| return; |
| } |
| } |
| /* Add it to the list of calls. */ |
| rtegraph_include_function_call (func); |
| } |
| |
| |
| /* examine_function_decl, check if the current function is a module |
| constructor/deconstructor. Also check if the current function is |
| declared as external. */ |
| |
| static void |
| examine_function_decl (rtenode *rt) |
| { |
| tree fndecl = rtegraph_get_func (rt); |
| if (fndecl != NULL && (DECL_NAME (fndecl) != NULL)) |
| { |
| /* Check if the function is a module constructor. */ |
| if (is_constructor (fndecl)) |
| rtegraph_constructors_include (rt); |
| /* Can it be called externally? */ |
| if (is_external (fndecl)) |
| rtegraph_externs_include (rt); |
| } |
| } |
| |
| |
| /* Check and warn if STMT is a self-assign statement. */ |
| |
| static void |
| runtime_exception_inevitable (gimple *stmt) |
| { |
| if (is_gimple_call (stmt)) |
| examine_call (stmt); |
| } |
| |
| |
| namespace { |
| |
| const pass_data pass_data_exception_detection = |
| { |
| GIMPLE_PASS, /* type */ |
| "runtime_exception_inevitable", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| PROP_gimple_lcf , /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_warn_exception_inevitable : public gimple_opt_pass |
| { |
| public: |
| pass_warn_exception_inevitable(gcc::context *ctxt) |
| : gimple_opt_pass(pass_data_exception_detection, ctxt) |
| {} |
| |
| virtual unsigned int execute (function *); |
| }; |
| |
| /* execute checks the first basic block of function fun to see if it |
| calls a runtime exception. */ |
| |
| unsigned int |
| pass_warn_exception_inevitable::execute (function *fun) |
| { |
| gimple_stmt_iterator gsi; |
| basic_block bb; |
| /* Record a function declaration. */ |
| rtenode *fn = rtegraph_lookup (fun->gimple_body, fun->decl, false); |
| |
| rtegraph_set_current_function (fn); |
| /* Check if the current function is a module constructor/deconstructor. |
| Also check if the current function is declared as external. */ |
| examine_function_decl (fn); |
| |
| #if defined(DEBUG_BASICBLOCK) |
| pretty_function (fun->decl); |
| int basic_count = 0; |
| #endif |
| FOR_EACH_BB_FN (bb, fun) |
| { |
| #if defined(DEBUG_BASICBLOCK) |
| int stmt_count = 0; |
| #endif |
| for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| #if defined(DEBUG_BASICBLOCK) |
| printf (" [%d][%d] [basic block][statement]\n", |
| basic_count, stmt_count); |
| stmt_count++; |
| #endif |
| runtime_exception_inevitable (gsi_stmt (gsi)); |
| #if defined(DEBUG_BASICBLOCK) |
| debug (gsi_stmt (gsi)); |
| #endif |
| } |
| /* We only care about the first basic block in each function. |
| We could continue to search if this edge falls though (top |
| of a loop for example) but for now this is cautiously safe. |
| --fixme-- */ |
| return 0; |
| #if defined(DEBUG_BASICBLOCK) |
| basic_count++; |
| #endif |
| } |
| return 0; |
| } |
| |
| /* analyse_graph discovers any reachable call to a runtime exception in the |
| first basic block of a reachable function. It then calls rtegraph_finish |
| to tidy up and return all dynamic memory used. */ |
| |
| void analyse_graph (void *gcc_data, void *user_data) |
| { |
| rtegraph_discover (); |
| rtegraph_finish (); |
| } |
| |
| } // anon namespace |
| |
| |
| static gimple_opt_pass * |
| make_pass_warn_exception_inevitable (gcc::context *ctxt) |
| { |
| return new pass_warn_exception_inevitable (ctxt); |
| } |
| |
| |
| /* plugin_init, check the version and register the plugin. */ |
| |
| int |
| plugin_init (struct plugin_name_args *plugin_info, |
| struct plugin_gcc_version *version) |
| { |
| struct register_pass_info pass_info; |
| const char *plugin_name = plugin_info->base_name; |
| |
| if (!plugin_default_version_check (version, &gcc_version)) |
| { |
| fprintf (stderr, "incorrect GCC version (%s) this plugin was built for GCC version %s\n", |
| version->basever, gcc_version.basever); |
| return 1; |
| } |
| |
| /* Runtime exception inevitable detection. This plugin is most effective if |
| it is run after all optimizations. This is plugged in at the end of |
| gimple range of optimizations. */ |
| pass_info.pass = make_pass_warn_exception_inevitable (g); |
| pass_info.reference_pass_name = "*warn_function_noreturn"; |
| |
| pass_info.ref_pass_instance_number = 1; |
| pass_info.pos_op = PASS_POS_INSERT_AFTER; |
| |
| rtegraph_init (); |
| |
| register_callback (plugin_name, |
| PLUGIN_PASS_MANAGER_SETUP, |
| NULL, |
| &pass_info); |
| register_callback (plugin_name, |
| PLUGIN_FINISH, analyse_graph, NULL); |
| return 0; |
| } |