view gcc/sancov.c @ 19:2b5abeee2509 default tip

update gcc11
author anatofuz
date Mon, 25 May 2020 07:50:57 +0900
parents 1830386684a0
children
line wrap: on
line source

/* Code coverage instrumentation for fuzzing.
   Copyright (C) 2015-2020 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);
}