/* { dg-options "-O" } */ /* This plugin exercises the path-printing code. The goal is to unit-test the path-printing code without needing any specific tests within the compiler's IR. We can't use any real diagnostics for this, so we have to fake it, hence this plugin. */ #include "gcc-plugin.h" #include "config.h" #include "system.h" #include "coretypes.h" #include "tm.h" #include "tree.h" #include "stringpool.h" #include "toplev.h" #include "basic-block.h" #include "hash-table.h" #include "vec.h" #include "ggc.h" #include "basic-block.h" #include "tree-ssa-alias.h" #include "internal-fn.h" #include "gimple.h" #include "gimple-iterator.h" #include "gimple-fold.h" #include "tree-eh.h" #include "gimple-expr.h" #include "is-a.h" #include "tree.h" #include "tree-pass.h" #include "intl.h" #include "plugin-version.h" #include "diagnostic.h" #include "diagnostic-path.h" #include "diagnostic-metadata.h" #include "context.h" #include "print-tree.h" #include "gcc-rich-location.h" #include "cgraph.h" #include "simple-diagnostic-path.h" int plugin_is_GPL_compatible; const pass_data pass_data_test_show_path = { IPA_PASS, /* type */ "test_show_path", /* name */ OPTGROUP_NONE, /* optinfo_flags */ TV_NONE, /* tv_id */ PROP_ssa, /* properties_required */ 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ 0, /* todo_flags_finish */ }; class pass_test_show_path : public ipa_opt_pass_d { public: pass_test_show_path(gcc::context *ctxt) : ipa_opt_pass_d (pass_data_test_show_path, ctxt, NULL, /* generate_summary */ NULL, /* write_summary */ NULL, /* read_summary */ NULL, /* write_optimization_summary */ NULL, /* read_optimization_summary */ NULL, /* stmt_fixup */ 0, /* function_transform_todo_flags_start */ NULL, /* function_transform */ NULL) /* variable_transform */ {} /* opt_pass methods: */ bool gate (function *) { return true; } virtual unsigned int execute (function *); }; // class pass_test_show_path /* Determine if STMT is a call with NUM_ARGS arguments to a function named FUNCNAME. If so, return STMT as a gcall *. Otherwise return NULL. */ static gcall * check_for_named_call (gimple *stmt, const char *funcname, unsigned int num_args) { gcc_assert (funcname); gcall *call = dyn_cast (stmt); if (!call) return NULL; tree fndecl = gimple_call_fndecl (call); if (!fndecl) return NULL; if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname)) return NULL; if (gimple_call_num_args (call) != num_args) { error_at (stmt->location, "expected number of args: %i (got %i)", num_args, gimple_call_num_args (call)); return NULL; } return call; } /* Example 1: a purely intraprocedural path. */ static void example_1 () { gimple_stmt_iterator gsi; basic_block bb; gcall *call_to_PyList_Append = NULL; gcall *call_to_PyList_New = NULL; gcond *for_cond = NULL; function *example_a_fun = NULL; cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) { function *fun = node->get_fun (); FOR_EACH_BB_FN (bb, fun) { for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { gimple *stmt = gsi_stmt (gsi); if (gcall *call = check_for_named_call (stmt, "PyList_New", 1)) { call_to_PyList_New = call; example_a_fun = fun; } if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2)) call_to_PyList_Append = call; if (gcond *cond = dyn_cast (stmt)) for_cond = cond; } } } if (call_to_PyList_New && for_cond && call_to_PyList_Append) { auto_diagnostic_group d; gcc_rich_location richloc (gimple_location (call_to_PyList_Append)); tree_logical_location_manager logical_loc_mgr; simple_diagnostic_path path (logical_loc_mgr, global_dc->get_reference_printer ()); diagnostic_event_id_t alloc_event_id = path.add_event (gimple_location (call_to_PyList_New), example_a_fun->decl, 0, "when %qs fails, returning NULL", "PyList_New"); path.add_event (gimple_location (for_cond), example_a_fun->decl, 0, "when %qs", "i < count"); path.add_event (gimple_location (call_to_PyList_Append), example_a_fun->decl, 0, "when calling %qs, passing NULL from %@ as argument %i", "PyList_Append", &alloc_event_id, 1); richloc.set_path (&path); error_at (&richloc, "passing NULL as argument %i to %qs" " which requires a non-NULL parameter", 1, "PyList_Append"); } } /* A (function, location_t) pair. */ struct event_location_t { event_location_t () : m_fun (NULL), m_loc (UNKNOWN_LOCATION) {} event_location_t (function *fun, location_t loc) : m_fun (fun), m_loc (loc) {} void set (const gimple *stmt, function *fun) { m_fun = fun; m_loc = gimple_location (stmt); } function *m_fun; location_t m_loc; }; /* If FUN's name matches FUNCNAME, write the function and its start location into *OUT_ENTRY. */ static bool check_for_named_function (function *fun, const char *funcname, event_location_t *out_entry) { gcc_assert (fun); gcc_assert (funcname); if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname)) return false; *out_entry = event_location_t (fun, fun->function_start_locus); return true; } /* Example 2: an interprocedural path. */ class test_diagnostic_path : public simple_diagnostic_path { public: test_diagnostic_path (pretty_printer *event_pp) : simple_diagnostic_path (m_logical_loc_mgr, event_pp) { } diagnostic_event_id_t add_event_2 (event_location_t evloc, int stack_depth, const char *desc, diagnostic_thread_id_t thread_id = 0) { gcc_assert (evloc.m_fun); return add_thread_event (thread_id, evloc.m_loc, evloc.m_fun->decl, stack_depth, desc); } diagnostic_event_id_t add_event_2_with_event_id (event_location_t evloc, int stack_depth, const char *fmt, diagnostic_thread_id_t thread_id, diagnostic_event_id_t event_id) { gcc_assert (evloc.m_fun); // FMT is assumed to have a single %@ argument return add_thread_event (thread_id, evloc.m_loc, evloc.m_fun->decl, stack_depth, fmt, &event_id); } void add_entry (event_location_t evloc, int stack_depth, const char *funcname, diagnostic_thread_id_t thread_id = 0) { gcc_assert (evloc.m_fun); add_thread_event (thread_id, evloc.m_loc, evloc.m_fun->decl, stack_depth, "entering %qs", funcname); } void add_call (event_location_t call_evloc, int caller_stack_depth, event_location_t callee_entry_evloc, const char *callee) { gcc_assert (call_evloc.m_fun); add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth, "calling %qs", callee); add_entry (callee_entry_evloc, caller_stack_depth + 1, callee); } void add_leaf_call (event_location_t call_evloc, int caller_stack_depth, const char *callee) { gcc_assert (call_evloc.m_fun); add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth, "calling %qs", callee); } private: tree_logical_location_manager m_logical_loc_mgr; }; static void example_2 () { gimple_stmt_iterator gsi; basic_block bb; event_location_t entry_to_wrapped_malloc; event_location_t call_to_malloc; event_location_t entry_to_wrapped_free; event_location_t call_to_free; event_location_t entry_to_make_boxed_int; event_location_t call_to_wrapped_malloc; event_location_t entry_to_free_boxed_int; event_location_t call_to_wrapped_free; event_location_t entry_to_test; event_location_t call_to_make_boxed_int; event_location_t call_to_free_boxed_int; event_location_t call_to_missing_location; cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) { function *fun = node->get_fun (); FOR_EACH_BB_FN (bb, fun) { check_for_named_function (fun, "wrapped_malloc", &entry_to_wrapped_malloc); check_for_named_function (fun, "wrapped_free", &entry_to_wrapped_free); check_for_named_function (fun, "make_boxed_int", &entry_to_make_boxed_int); check_for_named_function (fun, "free_boxed_int", &entry_to_free_boxed_int); check_for_named_function (fun, "test", &entry_to_test); for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { gimple *stmt = gsi_stmt (gsi); if (gcall *call = check_for_named_call (stmt, "malloc", 1)) call_to_malloc.set (call, fun); if (gcall *call = check_for_named_call (stmt, "free", 1)) call_to_free.set (call, fun); if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1)) call_to_wrapped_malloc.set (call, fun); if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1)) call_to_wrapped_free.set (call, fun); if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1)) call_to_make_boxed_int.set (call, fun); if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1)) call_to_free_boxed_int.set (call, fun); if (gcall *call = check_for_named_call (stmt, "missing_location", 0)) { call_to_missing_location.set (call, fun); /* Simulate an event that's missing a useful location_t. */ call_to_missing_location.m_loc = UNKNOWN_LOCATION; } } } } if (call_to_malloc.m_fun) { auto_diagnostic_group d; gcc_rich_location richloc (call_to_free.m_loc); test_diagnostic_path path (global_dc->get_reference_printer ()); path.add_entry (entry_to_test, 0, "test"); path.add_call (call_to_make_boxed_int, 0, entry_to_make_boxed_int, "make_boxed_int"); path.add_call (call_to_wrapped_malloc, 1, entry_to_wrapped_malloc, "wrapped_malloc"); path.add_leaf_call (call_to_malloc, 2, "malloc"); for (int i = 0; i < 2; i++) { path.add_call (call_to_free_boxed_int, 0, entry_to_free_boxed_int, "free_boxed_int"); path.add_call (call_to_wrapped_free, 1, entry_to_wrapped_free, "wrapped_free"); path.add_leaf_call (call_to_free, 2, "free"); if (i == 0 && call_to_missing_location.m_fun) path.add_leaf_call (call_to_missing_location, 0, "missing_location"); } richloc.set_path (&path); diagnostic_metadata m; m.add_cwe (415); /* CWE-415: Double Free. */ warning_meta (&richloc, m, 0, "double-free of %qs", "ptr"); } } /* Example 3: an interprocedural path with a callback. */ static void example_3 () { gimple_stmt_iterator gsi; basic_block bb; event_location_t entry_to_custom_logger; event_location_t call_to_fprintf; event_location_t entry_to_int_handler; event_location_t call_to_custom_logger; event_location_t entry_to_register_handler; event_location_t call_to_signal; event_location_t entry_to_test; event_location_t call_to_register_handler; cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) { function *fun = node->get_fun (); FOR_EACH_BB_FN (bb, fun) { check_for_named_function (fun, "custom_logger", &entry_to_custom_logger); check_for_named_function (fun, "int_handler", &entry_to_int_handler); check_for_named_function (fun, "register_handler", &entry_to_register_handler); check_for_named_function (fun, "test", &entry_to_test); for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { gimple *stmt = gsi_stmt (gsi); if (gcall *call = check_for_named_call (stmt, "fprintf", 3)) call_to_fprintf.set (call, fun); if (gcall *call = check_for_named_call (stmt, "custom_logger", 1)) call_to_custom_logger.set (call, fun); if (gcall *call = check_for_named_call (stmt, "register_handler", 0)) call_to_register_handler.set (call, fun); if (gcall *call = check_for_named_call (stmt, "signal", 2)) call_to_signal.set (call, fun); } } } if (call_to_fprintf.m_fun) { auto_diagnostic_group d; gcc_rich_location richloc (call_to_fprintf.m_loc); test_diagnostic_path path (global_dc->get_reference_printer ()); path.add_entry (entry_to_test, 1, "test"); path.add_call (call_to_register_handler, 1, entry_to_register_handler, "register_handler"); path.add_event (call_to_signal.m_loc, call_to_signal.m_fun->decl, 2, "registering 'int_handler' as signal handler"); path.add_event (UNKNOWN_LOCATION, NULL_TREE, 0, "later on, when the signal is delivered to the process"); path.add_entry (entry_to_int_handler, 1, "int_handler"); path.add_call (call_to_custom_logger, 1, entry_to_custom_logger, "custom_logger"); path.add_leaf_call (call_to_fprintf, 2, "fprintf"); richloc.set_path (&path); diagnostic_metadata m; /* CWE-479: Signal Handler Use of a Non-reentrant Function. */ m.add_cwe (479); warning_meta (&richloc, m, 0, "call to %qs from within signal handler", "fprintf"); } } /* Example 4: a multithreaded path. */ static void example_4 () { gimple_stmt_iterator gsi; basic_block bb; event_location_t entry_to_foo; event_location_t entry_to_bar; event_location_t call_to_acquire_lock_a_in_foo; event_location_t call_to_acquire_lock_b_in_foo; event_location_t call_to_acquire_lock_a_in_bar; event_location_t call_to_acquire_lock_b_in_bar; cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) { function *fun = node->get_fun (); FOR_EACH_BB_FN (bb, fun) { bool in_foo = check_for_named_function (fun, "foo", &entry_to_foo); bool in_bar = check_for_named_function (fun, "bar", &entry_to_bar); for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { gimple *stmt = gsi_stmt (gsi); event_location_t *evloc = NULL; gcall *call = NULL; if (call = check_for_named_call (stmt, "acquire_lock_a", 0)) evloc = (in_foo ? &call_to_acquire_lock_a_in_foo : &call_to_acquire_lock_a_in_bar); else if (call = check_for_named_call (stmt, "acquire_lock_b", 0)) evloc = (in_foo ? &call_to_acquire_lock_b_in_foo : &call_to_acquire_lock_b_in_bar); if (evloc) evloc->set (call, fun); } } } if (call_to_acquire_lock_a_in_foo.m_fun) { auto_diagnostic_group d; gcc_rich_location richloc (call_to_acquire_lock_a_in_bar.m_loc); test_diagnostic_path path (global_dc->get_reference_printer ()); diagnostic_thread_id_t thread_1 = path.add_thread ("Thread 1"); diagnostic_thread_id_t thread_2 = path.add_thread ("Thread 2"); path.add_entry (entry_to_foo, 0, "foo", thread_1); diagnostic_event_id_t event_a_acquired = path.add_event_2 (call_to_acquire_lock_a_in_foo, 1, "lock a is now held by thread 1", thread_1); path.add_entry (entry_to_bar, 0, "bar", thread_2); diagnostic_event_id_t event_b_acquired = path.add_event_2 (call_to_acquire_lock_b_in_bar, 1, "lock b is now held by thread 2", thread_2); path.add_event_2_with_event_id (call_to_acquire_lock_b_in_foo, 1, "deadlocked due to waiting for lock b in thread 1" " (acquired by thread 2 at %@)...", thread_1, event_b_acquired); path.add_event_2_with_event_id (call_to_acquire_lock_a_in_bar, 1, "...whilst waiting for lock a in thread 2" " (acquired by thread 1 at %@)", thread_2, event_a_acquired); richloc.set_path (&path); diagnostic_metadata m; warning_meta (&richloc, m, 0, "deadlock due to inconsistent lock acquisition order"); } } unsigned int pass_test_show_path::execute (function *) { example_1 (); example_2 (); example_3 (); example_4 (); return 0; } static opt_pass * make_pass_test_show_path (gcc::context *ctxt) { return new pass_test_show_path (ctxt); } 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; int argc = plugin_info->argc; struct plugin_argument *argv = plugin_info->argv; if (!plugin_default_version_check (version, &gcc_version)) return 1; global_dc->m_source_printing.max_width = 80; pass_info.pass = make_pass_test_show_path (g); pass_info.reference_pass_name = "whole-program"; pass_info.ref_pass_instance_number = 1; pass_info.pos_op = PASS_POS_INSERT_BEFORE; register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); return 0; }