| /* Code coverage instrumentation for fuzzing. |
| Copyright (C) 2015-2021 Free Software Foundation, Inc. |
| Contributed by Dmitry Vyukov <dvyukov@google.com> and |
| Wish Wu <wishwu007@gmail.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 "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "basic-block.h" |
| #include "options.h" |
| #include "flags.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "stmt.h" |
| #include "gimple-iterator.h" |
| #include "gimple-builder.h" |
| #include "tree-cfg.h" |
| #include "tree-pass.h" |
| #include "tree-iterator.h" |
| #include "fold-const.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "output.h" |
| #include "cgraph.h" |
| #include "asan.h" |
| |
| namespace { |
| |
| /* Instrument one comparison operation, which compares lhs and rhs. |
| Call the instrumentation function with the comparison operand. |
| For integral comparisons if exactly one of the comparison operands is |
| constant, call __sanitizer_cov_trace_const_cmp* instead of |
| __sanitizer_cov_trace_cmp*. */ |
| |
| static void |
| instrument_comparison (gimple_stmt_iterator *gsi, tree lhs, tree rhs) |
| { |
| tree type = TREE_TYPE (lhs); |
| enum built_in_function fncode = END_BUILTINS; |
| tree to_type = NULL_TREE; |
| bool c = false; |
| |
| if (INTEGRAL_TYPE_P (type)) |
| { |
| c = (is_gimple_min_invariant (lhs) |
| ^ is_gimple_min_invariant (rhs)); |
| switch (int_size_in_bytes (type)) |
| { |
| case 1: |
| fncode = c ? BUILT_IN_SANITIZER_COV_TRACE_CONST_CMP1 |
| : BUILT_IN_SANITIZER_COV_TRACE_CMP1; |
| to_type = unsigned_char_type_node; |
| break; |
| case 2: |
| fncode = c ? BUILT_IN_SANITIZER_COV_TRACE_CONST_CMP2 |
| : BUILT_IN_SANITIZER_COV_TRACE_CMP2; |
| to_type = uint16_type_node; |
| break; |
| case 4: |
| fncode = c ? BUILT_IN_SANITIZER_COV_TRACE_CONST_CMP4 |
| : BUILT_IN_SANITIZER_COV_TRACE_CMP4; |
| to_type = uint32_type_node; |
| break; |
| default: |
| fncode = c ? BUILT_IN_SANITIZER_COV_TRACE_CONST_CMP8 |
| : BUILT_IN_SANITIZER_COV_TRACE_CMP8; |
| to_type = uint64_type_node; |
| break; |
| } |
| } |
| else if (SCALAR_FLOAT_TYPE_P (type)) |
| { |
| if (TYPE_MODE (type) == TYPE_MODE (float_type_node)) |
| { |
| fncode = BUILT_IN_SANITIZER_COV_TRACE_CMPF; |
| to_type = float_type_node; |
| } |
| else if (TYPE_MODE (type) == TYPE_MODE (double_type_node)) |
| { |
| fncode = BUILT_IN_SANITIZER_COV_TRACE_CMPD; |
| to_type = double_type_node; |
| } |
| } |
| |
| if (to_type != NULL_TREE) |
| { |
| gimple_seq seq = NULL; |
| |
| if (!useless_type_conversion_p (to_type, type)) |
| { |
| if (TREE_CODE (lhs) == INTEGER_CST) |
| lhs = fold_convert (to_type, lhs); |
| else |
| { |
| gimple_seq_add_stmt (&seq, build_type_cast (to_type, lhs)); |
| lhs = gimple_assign_lhs (gimple_seq_last_stmt (seq)); |
| } |
| |
| if (TREE_CODE (rhs) == INTEGER_CST) |
| rhs = fold_convert (to_type, rhs); |
| else |
| { |
| gimple_seq_add_stmt (&seq, build_type_cast (to_type, rhs)); |
| rhs = gimple_assign_lhs (gimple_seq_last_stmt (seq)); |
| } |
| } |
| |
| if (c && !is_gimple_min_invariant (lhs)) |
| std::swap (lhs, rhs); |
| |
| tree fndecl = builtin_decl_implicit (fncode); |
| gimple *gcall = gimple_build_call (fndecl, 2, lhs, rhs); |
| gimple_seq_add_stmt (&seq, gcall); |
| |
| gimple_seq_set_location (seq, gimple_location (gsi_stmt (*gsi))); |
| gsi_insert_seq_before (gsi, seq, GSI_SAME_STMT); |
| } |
| } |
| |
| /* Instrument switch statement. Call __sanitizer_cov_trace_switch with |
| the value of the index and array that contains number of case values, |
| the bitsize of the index and the case values converted to uint64_t. */ |
| |
| static void |
| instrument_switch (gimple_stmt_iterator *gsi, gimple *stmt, function *fun) |
| { |
| gswitch *switch_stmt = as_a<gswitch *> (stmt); |
| tree index = gimple_switch_index (switch_stmt); |
| HOST_WIDE_INT size_in_bytes = int_size_in_bytes (TREE_TYPE (index)); |
| if (size_in_bytes == -1 || size_in_bytes > 8) |
| return; |
| |
| location_t loc = gimple_location (stmt); |
| unsigned i, n = gimple_switch_num_labels (switch_stmt), num = 0; |
| for (i = 1; i < n; ++i) |
| { |
| tree label = gimple_switch_label (switch_stmt, i); |
| |
| tree low_case = CASE_LOW (label); |
| if (low_case != NULL_TREE) |
| num++; |
| |
| tree high_case = CASE_HIGH (label); |
| if (high_case != NULL_TREE) |
| num++; |
| } |
| |
| tree case_array_type |
| = build_array_type (build_type_variant (uint64_type_node, 1, 0), |
| build_index_type (size_int (num + 2 - 1))); |
| |
| char name[64]; |
| static size_t case_array_count = 0; |
| ASM_GENERATE_INTERNAL_LABEL (name, "LCASEARRAY", case_array_count++); |
| tree case_array_var = build_decl (loc, VAR_DECL, get_identifier (name), |
| case_array_type); |
| TREE_STATIC (case_array_var) = 1; |
| TREE_PUBLIC (case_array_var) = 0; |
| TREE_CONSTANT (case_array_var) = 1; |
| TREE_READONLY (case_array_var) = 1; |
| DECL_EXTERNAL (case_array_var) = 0; |
| DECL_ARTIFICIAL (case_array_var) = 1; |
| DECL_IGNORED_P (case_array_var) = 1; |
| |
| vec <constructor_elt, va_gc> *v = NULL; |
| vec_alloc (v, num + 2); |
| CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, |
| build_int_cst (uint64_type_node, num)); |
| CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, |
| build_int_cst (uint64_type_node, |
| size_in_bytes * BITS_PER_UNIT)); |
| for (i = 1; i < n; ++i) |
| { |
| tree label = gimple_switch_label (switch_stmt, i); |
| |
| tree low_case = CASE_LOW (label); |
| if (low_case != NULL_TREE) |
| CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, |
| fold_convert (uint64_type_node, low_case)); |
| |
| tree high_case = CASE_HIGH (label); |
| if (high_case != NULL_TREE) |
| CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, |
| fold_convert (uint64_type_node, high_case)); |
| } |
| tree ctor = build_constructor (case_array_type, v); |
| TREE_STATIC (ctor) = 1; |
| TREE_PUBLIC (ctor) = 0; |
| TREE_CONSTANT (ctor) = 1; |
| TREE_READONLY (ctor) = 1; |
| DECL_INITIAL (case_array_var) = ctor; |
| varpool_node::finalize_decl (case_array_var); |
| add_local_decl (fun, case_array_var); |
| |
| gimple_seq seq = NULL; |
| |
| if (!useless_type_conversion_p (uint64_type_node, TREE_TYPE (index))) |
| { |
| if (TREE_CODE (index) == INTEGER_CST) |
| index = fold_convert (uint64_type_node, index); |
| else |
| { |
| gimple_seq_add_stmt (&seq, build_type_cast (uint64_type_node, index)); |
| index = gimple_assign_lhs (gimple_seq_last_stmt (seq)); |
| } |
| } |
| |
| tree fndecl = builtin_decl_implicit (BUILT_IN_SANITIZER_COV_TRACE_SWITCH); |
| gimple *gcall = gimple_build_call (fndecl, 2, index, |
| build_fold_addr_expr (case_array_var)); |
| gimple_seq_add_stmt (&seq, gcall); |
| |
| gimple_seq_set_location (seq, loc); |
| gsi_insert_seq_before (gsi, seq, GSI_SAME_STMT); |
| } |
| |
| unsigned |
| sancov_pass (function *fun) |
| { |
| initialize_sanitizer_builtins (); |
| |
| /* Insert callback into beginning of every BB. */ |
| if (flag_sanitize_coverage & SANITIZE_COV_TRACE_PC) |
| { |
| basic_block bb; |
| tree fndecl = builtin_decl_implicit (BUILT_IN_SANITIZER_COV_TRACE_PC); |
| FOR_EACH_BB_FN (bb, fun) |
| { |
| gimple_stmt_iterator gsi = gsi_start_nondebug_after_labels_bb (bb); |
| if (gsi_end_p (gsi)) |
| continue; |
| gimple *stmt = gsi_stmt (gsi); |
| gimple *gcall = gimple_build_call (fndecl, 0); |
| gimple_set_location (gcall, gimple_location (stmt)); |
| gsi_insert_before (&gsi, gcall, GSI_SAME_STMT); |
| } |
| } |
| |
| /* Insert callback into every comparison related operation. */ |
| if (flag_sanitize_coverage & SANITIZE_COV_TRACE_CMP) |
| { |
| basic_block bb; |
| FOR_EACH_BB_FN (bb, fun) |
| { |
| gimple_stmt_iterator gsi; |
| for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| gimple *stmt = gsi_stmt (gsi); |
| enum tree_code rhs_code; |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_ASSIGN: |
| rhs_code = gimple_assign_rhs_code (stmt); |
| if (TREE_CODE_CLASS (rhs_code) == tcc_comparison) |
| instrument_comparison (&gsi, |
| gimple_assign_rhs1 (stmt), |
| gimple_assign_rhs2 (stmt)); |
| else if (rhs_code == COND_EXPR |
| && COMPARISON_CLASS_P (gimple_assign_rhs1 (stmt))) |
| { |
| tree cond = gimple_assign_rhs1 (stmt); |
| instrument_comparison (&gsi, TREE_OPERAND (cond, 0), |
| TREE_OPERAND (cond, 1)); |
| } |
| break; |
| case GIMPLE_COND: |
| instrument_comparison (&gsi, |
| gimple_cond_lhs (stmt), |
| gimple_cond_rhs (stmt)); |
| break; |
| |
| case GIMPLE_SWITCH: |
| instrument_switch (&gsi, stmt, fun); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| template <bool O0> class pass_sancov : public gimple_opt_pass |
| { |
| public: |
| pass_sancov (gcc::context *ctxt) : gimple_opt_pass (data, ctxt) {} |
| |
| static const pass_data data; |
| opt_pass * |
| clone () |
| { |
| return new pass_sancov<O0> (m_ctxt); |
| } |
| virtual bool |
| gate (function *) |
| { |
| return flag_sanitize_coverage && (!O0 || !optimize); |
| } |
| virtual unsigned int |
| execute (function *fun) |
| { |
| return sancov_pass (fun); |
| } |
| }; // class pass_sancov |
| |
| template <bool O0> |
| const pass_data pass_sancov<O0>::data = { |
| GIMPLE_PASS, /* type */ |
| O0 ? "sancov_O0" : "sancov", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| (PROP_cfg), /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_update_ssa, /* todo_flags_finish */ |
| }; |
| |
| } // anon namespace |
| |
| gimple_opt_pass * |
| make_pass_sancov (gcc::context *ctxt) |
| { |
| return new pass_sancov<false> (ctxt); |
| } |
| |
| gimple_opt_pass * |
| make_pass_sancov_O0 (gcc::context *ctxt) |
| { |
| return new pass_sancov<true> (ctxt); |
| } |