summaryrefslogtreecommitdiff
path: root/gcc/ubsan.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/ubsan.c')
-rw-r--r--gcc/ubsan.c337
1 files changed, 278 insertions, 59 deletions
diff --git a/gcc/ubsan.c b/gcc/ubsan.c
index a16f3eb097f..7cc8c180ba8 100644
--- a/gcc/ubsan.c
+++ b/gcc/ubsan.c
@@ -1,5 +1,5 @@
/* UndefinedBehaviorSanitizer, undefined behavior detector.
- Copyright (C) 2013 Free Software Foundation, Inc.
+ Copyright (C) 2013-2014 Free Software Foundation, Inc.
Contributed by Marek Polacek <polacek@redhat.com>
This file is part of GCC.
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see
#include "cgraph.h"
#include "tree-pass.h"
#include "tree-ssa-alias.h"
+#include "tree-pretty-print.h"
#include "internal-fn.h"
#include "gimple-expr.h"
#include "gimple.h"
@@ -40,9 +41,11 @@ along with GCC; see the file COPYING3. If not see
#include "cfgloop.h"
#include "ubsan.h"
#include "c-family/c-common.h"
-
-/* From trans-mem.c. */
-#define PROB_VERY_UNLIKELY (REG_BR_PROB_BASE / 2000 - 1)
+#include "rtl.h"
+#include "expr.h"
+#include "tree-ssanames.h"
+#include "asan.h"
+#include "gimplify-me.h"
/* Map from a tree to a VAR_DECL tree. */
@@ -105,45 +108,53 @@ decl_for_type_insert (tree type, tree decl)
/* Helper routine, which encodes a value in the pointer_sized_int_node.
Arguments with precision <= POINTER_SIZE are passed directly,
- the rest is passed by reference. T is a value we are to encode. */
+ the rest is passed by reference. T is a value we are to encode.
+ IN_EXPAND_P is true if this function is called during expansion. */
tree
-ubsan_encode_value (tree t)
+ubsan_encode_value (tree t, bool in_expand_p)
{
tree type = TREE_TYPE (t);
- switch (TREE_CODE (type))
- {
- case INTEGER_TYPE:
- if (TYPE_PRECISION (type) <= POINTER_SIZE)
+ const unsigned int bitsize = GET_MODE_BITSIZE (TYPE_MODE (type));
+ if (bitsize <= POINTER_SIZE)
+ switch (TREE_CODE (type))
+ {
+ case BOOLEAN_TYPE:
+ case ENUMERAL_TYPE:
+ case INTEGER_TYPE:
return fold_build1 (NOP_EXPR, pointer_sized_int_node, t);
+ case REAL_TYPE:
+ {
+ tree itype = build_nonstandard_integer_type (bitsize, true);
+ t = fold_build1 (VIEW_CONVERT_EXPR, itype, t);
+ return fold_convert (pointer_sized_int_node, t);
+ }
+ default:
+ gcc_unreachable ();
+ }
+ else
+ {
+ if (!DECL_P (t) || !TREE_ADDRESSABLE (t))
+ {
+ /* The reason for this is that we don't want to pessimize
+ code by making vars unnecessarily addressable. */
+ tree var = create_tmp_var (type, NULL);
+ tree tem = build2 (MODIFY_EXPR, void_type_node, var, t);
+ if (in_expand_p)
+ {
+ rtx mem
+ = assign_stack_temp_for_type (TYPE_MODE (type),
+ GET_MODE_SIZE (TYPE_MODE (type)),
+ type);
+ SET_DECL_RTL (var, mem);
+ expand_assignment (var, t, false);
+ return build_fold_addr_expr (var);
+ }
+ t = build_fold_addr_expr (var);
+ return build2 (COMPOUND_EXPR, TREE_TYPE (t), tem, t);
+ }
else
return build_fold_addr_expr (t);
- case REAL_TYPE:
- {
- unsigned int bitsize = GET_MODE_BITSIZE (TYPE_MODE (type));
- if (bitsize <= POINTER_SIZE)
- {
- tree itype = build_nonstandard_integer_type (bitsize, true);
- t = fold_build1 (VIEW_CONVERT_EXPR, itype, t);
- return fold_convert (pointer_sized_int_node, t);
- }
- else
- {
- if (!TREE_ADDRESSABLE (t))
- {
- /* The reason for this is that we don't want to pessimize
- code by making vars unnecessarily addressable. */
- tree var = create_tmp_var (TREE_TYPE (t), NULL);
- tree tem = build2 (MODIFY_EXPR, void_type_node, var, t);
- t = build_fold_addr_expr (var);
- return build2 (COMPOUND_EXPR, TREE_TYPE (t), tem, t);
- }
- else
- return build_fold_addr_expr (t);
- }
- }
- default:
- gcc_unreachable ();
}
}
@@ -229,13 +240,13 @@ ubsan_source_location (location_t loc)
xloc = expand_location (loc);
/* Fill in the values from LOC. */
- size_t len = xloc.file ? strlen (xloc.file) : 0;
- tree str = build_string (len + 1, xloc.file ? xloc.file : "");
+ size_t len = strlen (xloc.file);
+ tree str = build_string (len + 1, xloc.file);
TREE_TYPE (str) = build_array_type (char_type_node,
build_index_type (size_int (len)));
TREE_READONLY (str) = 1;
TREE_STATIC (str) = 1;
- str = build_fold_addr_expr_loc (loc, str);
+ str = build_fold_addr_expr (str);
tree ctor = build_constructor_va (type, 3, NULL_TREE, str, NULL_TREE,
build_int_cst (unsigned_type_node,
xloc.line), NULL_TREE,
@@ -272,8 +283,12 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p)
type = TYPE_MAIN_VARIANT (type);
tree decl = decl_for_type_lookup (type);
- if (decl != NULL_TREE)
- return decl;
+ /* It is possible that some of the earlier created DECLs were found
+ unused, in that case they weren't emitted and varpool_get_node
+ returns NULL node on them. But now we really need them. Thus,
+ renew them here. */
+ if (decl != NULL_TREE && varpool_get_node (decl))
+ return build_fold_addr_expr (decl);
tree dtype = ubsan_type_descriptor_type ();
tree type2 = type;
@@ -332,6 +347,8 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p)
switch (TREE_CODE (type))
{
+ case BOOLEAN_TYPE:
+ case ENUMERAL_TYPE:
case INTEGER_TYPE:
tkind = 0x0000;
break;
@@ -372,11 +389,10 @@ ubsan_type_descriptor (tree type, bool want_pointer_type_p)
DECL_INITIAL (decl) = ctor;
rest_of_decl_compilation (decl, 1, 0);
- /* Save the address of the VAR_DECL into the hash table. */
- decl = build_fold_addr_expr (decl);
+ /* Save the VAR_DECL into the hash table. */
decl_for_type_insert (type, decl);
- return decl;
+ return build_fold_addr_expr (decl);
}
/* Create a structure for the ubsan library. NAME is a name of the new
@@ -398,6 +414,7 @@ ubsan_create_data (const char *name, location_t loc,
tree td_type = ubsan_type_descriptor_type ();
TYPE_READONLY (td_type) = 1;
td_type = build_pointer_type (td_type);
+ loc = LOCATION_LOCUS (loc);
/* Create the structure type. */
ret = make_node (RECORD_TYPE);
@@ -610,24 +627,213 @@ instrument_mem_ref (tree t, gimple_stmt_iterator *iter, bool is_lhs)
gsi_insert_before (iter, g, GSI_SAME_STMT);
}
-/* Callback function for the pointer instrumentation. */
+/* Perform the pointer instrumentation. */
-static tree
-instrument_null (tree *tp, int * /*walk_subtree*/, void *data)
+static void
+instrument_null (gimple_stmt_iterator gsi, bool is_lhs)
{
- tree t = *tp;
+ gimple stmt = gsi_stmt (gsi);
+ tree t = is_lhs ? gimple_get_lhs (stmt) : gimple_assign_rhs1 (stmt);
+ t = get_base_address (t);
const enum tree_code code = TREE_CODE (t);
- struct walk_stmt_info *wi = (struct walk_stmt_info *) data;
-
if (code == MEM_REF
&& TREE_CODE (TREE_OPERAND (t, 0)) == SSA_NAME)
- instrument_mem_ref (TREE_OPERAND (t, 0), &wi->gsi, wi->is_lhs);
+ instrument_mem_ref (TREE_OPERAND (t, 0), &gsi, is_lhs);
else if (code == ADDR_EXPR
&& POINTER_TYPE_P (TREE_TYPE (t))
&& TREE_CODE (TREE_TYPE (TREE_TYPE (t))) == METHOD_TYPE)
- instrument_member_call (&wi->gsi);
+ instrument_member_call (&gsi);
+}
+
+/* Build an ubsan builtin call for the signed-integer-overflow
+ sanitization. CODE says what kind of builtin are we building,
+ LOC is a location, LHSTYPE is the type of LHS, OP0 and OP1
+ are operands of the binary operation. */
+
+tree
+ubsan_build_overflow_builtin (tree_code code, location_t loc, tree lhstype,
+ tree op0, tree op1)
+{
+ tree data = ubsan_create_data ("__ubsan_overflow_data", loc, NULL,
+ ubsan_type_descriptor (lhstype, false),
+ NULL_TREE);
+ enum built_in_function fn_code;
+
+ switch (code)
+ {
+ case PLUS_EXPR:
+ fn_code = BUILT_IN_UBSAN_HANDLE_ADD_OVERFLOW;
+ break;
+ case MINUS_EXPR:
+ fn_code = BUILT_IN_UBSAN_HANDLE_SUB_OVERFLOW;
+ break;
+ case MULT_EXPR:
+ fn_code = BUILT_IN_UBSAN_HANDLE_MUL_OVERFLOW;
+ break;
+ case NEGATE_EXPR:
+ fn_code = BUILT_IN_UBSAN_HANDLE_NEGATE_OVERFLOW;
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ tree fn = builtin_decl_explicit (fn_code);
+ return build_call_expr_loc (loc, fn, 2 + (code != NEGATE_EXPR),
+ build_fold_addr_expr_loc (loc, data),
+ ubsan_encode_value (op0, true),
+ op1 ? ubsan_encode_value (op1, true)
+ : NULL_TREE);
+}
+
+/* Perform the signed integer instrumentation. GSI is the iterator
+ pointing at statement we are trying to instrument. */
- return NULL_TREE;
+static void
+instrument_si_overflow (gimple_stmt_iterator gsi)
+{
+ gimple stmt = gsi_stmt (gsi);
+ tree_code code = gimple_assign_rhs_code (stmt);
+ tree lhs = gimple_assign_lhs (stmt);
+ tree lhstype = TREE_TYPE (lhs);
+ tree a, b;
+ gimple g;
+
+ /* If this is not a signed operation, don't instrument anything here.
+ Also punt on bit-fields. */
+ if (!INTEGRAL_TYPE_P (lhstype)
+ || TYPE_OVERFLOW_WRAPS (lhstype)
+ || GET_MODE_BITSIZE (TYPE_MODE (lhstype)) != TYPE_PRECISION (lhstype))
+ return;
+
+ switch (code)
+ {
+ case MINUS_EXPR:
+ case PLUS_EXPR:
+ case MULT_EXPR:
+ /* Transform
+ i = u {+,-,*} 5;
+ into
+ i = UBSAN_CHECK_{ADD,SUB,MUL} (u, 5); */
+ a = gimple_assign_rhs1 (stmt);
+ b = gimple_assign_rhs2 (stmt);
+ g = gimple_build_call_internal (code == PLUS_EXPR
+ ? IFN_UBSAN_CHECK_ADD
+ : code == MINUS_EXPR
+ ? IFN_UBSAN_CHECK_SUB
+ : IFN_UBSAN_CHECK_MUL, 2, a, b);
+ gimple_call_set_lhs (g, lhs);
+ gsi_replace (&gsi, g, false);
+ break;
+ case NEGATE_EXPR:
+ /* Represent i = -u;
+ as
+ i = UBSAN_CHECK_SUB (0, u); */
+ a = build_int_cst (lhstype, 0);
+ b = gimple_assign_rhs1 (stmt);
+ g = gimple_build_call_internal (IFN_UBSAN_CHECK_SUB, 2, a, b);
+ gimple_call_set_lhs (g, lhs);
+ gsi_replace (&gsi, g, false);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Instrument loads from (non-bitfield) bool and C++ enum values
+ to check if the memory value is outside of the range of the valid
+ type values. */
+
+static void
+instrument_bool_enum_load (gimple_stmt_iterator *gsi)
+{
+ gimple stmt = gsi_stmt (*gsi);
+ tree rhs = gimple_assign_rhs1 (stmt);
+ tree type = TREE_TYPE (rhs);
+ tree minv = NULL_TREE, maxv = NULL_TREE;
+
+ if (TREE_CODE (type) == BOOLEAN_TYPE && (flag_sanitize & SANITIZE_BOOL))
+ {
+ minv = boolean_false_node;
+ maxv = boolean_true_node;
+ }
+ else if (TREE_CODE (type) == ENUMERAL_TYPE
+ && (flag_sanitize & SANITIZE_ENUM)
+ && TREE_TYPE (type) != NULL_TREE
+ && TREE_CODE (TREE_TYPE (type)) == INTEGER_TYPE
+ && (TYPE_PRECISION (TREE_TYPE (type))
+ < GET_MODE_PRECISION (TYPE_MODE (type))))
+ {
+ minv = TYPE_MIN_VALUE (TREE_TYPE (type));
+ maxv = TYPE_MAX_VALUE (TREE_TYPE (type));
+ }
+ else
+ return;
+
+ int modebitsize = GET_MODE_BITSIZE (TYPE_MODE (type));
+ HOST_WIDE_INT bitsize, bitpos;
+ tree offset;
+ enum machine_mode mode;
+ int volatilep = 0, unsignedp = 0;
+ tree base = get_inner_reference (rhs, &bitsize, &bitpos, &offset, &mode,
+ &unsignedp, &volatilep, false);
+ tree utype = build_nonstandard_integer_type (modebitsize, 1);
+
+ if ((TREE_CODE (base) == VAR_DECL && DECL_HARD_REGISTER (base))
+ || (bitpos % modebitsize) != 0
+ || bitsize != modebitsize
+ || GET_MODE_BITSIZE (TYPE_MODE (utype)) != modebitsize
+ || TREE_CODE (gimple_assign_lhs (stmt)) != SSA_NAME)
+ return;
+
+ location_t loc = gimple_location (stmt);
+ tree ptype = build_pointer_type (TREE_TYPE (rhs));
+ tree atype = reference_alias_ptr_type (rhs);
+ gimple g = gimple_build_assign (make_ssa_name (ptype, NULL),
+ build_fold_addr_expr (rhs));
+ gimple_set_location (g, loc);
+ gsi_insert_before (gsi, g, GSI_SAME_STMT);
+ tree mem = build2 (MEM_REF, utype, gimple_assign_lhs (g),
+ build_int_cst (atype, 0));
+ tree urhs = make_ssa_name (utype, NULL);
+ g = gimple_build_assign (urhs, mem);
+ gimple_set_location (g, loc);
+ gsi_insert_before (gsi, g, GSI_SAME_STMT);
+ minv = fold_convert (utype, minv);
+ maxv = fold_convert (utype, maxv);
+ if (!integer_zerop (minv))
+ {
+ g = gimple_build_assign_with_ops (MINUS_EXPR,
+ make_ssa_name (utype, NULL),
+ urhs, minv);
+ gimple_set_location (g, loc);
+ gsi_insert_before (gsi, g, GSI_SAME_STMT);
+ }
+
+ gimple_stmt_iterator gsi2 = *gsi;
+ basic_block then_bb, fallthru_bb;
+ *gsi = create_cond_insert_point (gsi, true, false, true,
+ &then_bb, &fallthru_bb);
+ g = gimple_build_cond (GT_EXPR, gimple_assign_lhs (g),
+ int_const_binop (MINUS_EXPR, maxv, minv),
+ NULL_TREE, NULL_TREE);
+ gimple_set_location (g, loc);
+ gsi_insert_after (gsi, g, GSI_NEW_STMT);
+
+ gimple_assign_set_rhs_with_ops (&gsi2, NOP_EXPR, urhs, NULL_TREE);
+ update_stmt (stmt);
+
+ tree data = ubsan_create_data ("__ubsan_invalid_value_data",
+ loc, NULL,
+ ubsan_type_descriptor (type, false),
+ NULL_TREE);
+ data = build_fold_addr_expr_loc (loc, data);
+ tree fn = builtin_decl_explicit (BUILT_IN_UBSAN_HANDLE_LOAD_INVALID_VALUE);
+
+ gsi2 = gsi_after_labels (then_bb);
+ tree val = force_gimple_operand_gsi (&gsi2, ubsan_encode_value (urhs),
+ true, NULL_TREE, true, GSI_SAME_STMT);
+ g = gimple_build_call (fn, 2, data, val);
+ gimple_set_location (g, loc);
+ gsi_insert_before (&gsi2, g, GSI_SAME_STMT);
}
/* Gate and execute functions for ubsan pass. */
@@ -638,11 +844,10 @@ ubsan_pass (void)
basic_block bb;
gimple_stmt_iterator gsi;
- FOR_EACH_BB (bb)
+ FOR_EACH_BB_FN (bb, cfun)
{
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);)
{
- struct walk_stmt_info wi;
gimple stmt = gsi_stmt (gsi);
if (is_gimple_debug (stmt) || gimple_clobber_p (stmt))
{
@@ -650,9 +855,22 @@ ubsan_pass (void)
continue;
}
- memset (&wi, 0, sizeof (wi));
- wi.gsi = gsi;
- walk_gimple_op (stmt, instrument_null, &wi);
+ if ((flag_sanitize & SANITIZE_SI_OVERFLOW)
+ && is_gimple_assign (stmt))
+ instrument_si_overflow (gsi);
+
+ if (flag_sanitize & SANITIZE_NULL)
+ {
+ if (gimple_store_p (stmt))
+ instrument_null (gsi, true);
+ if (gimple_assign_load_p (stmt))
+ instrument_null (gsi, false);
+ }
+
+ if (flag_sanitize & (SANITIZE_BOOL | SANITIZE_ENUM)
+ && gimple_assign_load_p (stmt))
+ instrument_bool_enum_load (&gsi);
+
gsi_next (&gsi);
}
}
@@ -662,7 +880,8 @@ ubsan_pass (void)
static bool
gate_ubsan (void)
{
- return flag_sanitize & SANITIZE_NULL;
+ return flag_sanitize & (SANITIZE_NULL | SANITIZE_SI_OVERFLOW
+ | SANITIZE_BOOL | SANITIZE_ENUM);
}
namespace {