From 247fbfafcc633f5e1feac75a95a07a71c44764c7 Mon Sep 17 00:00:00 2001 From: Maxim Ostapenko Date: Thu, 1 Jun 2017 16:21:11 +0300 Subject: [PATCH] Implement ISan: unsigned integer overflow checker. ISan in FE. Handle all s and u cases. Add unit and regression tests. Change-Id: I3f60f751598d904683264372541c2d3be0076768 --- gcc/builtins.c | 1 + gcc/c-family/c-common.c | 43 +++++++++-- gcc/c-family/c-ubsan.c | 87 +++++++++++++++++++++ gcc/c-family/c-ubsan.h | 4 + gcc/c/c-parser.c | 18 +++++ gcc/c/c-typeck.c | 74 ++++++++++++++++-- gcc/calls.c | 4 + gcc/cp/call.c | 1 + gcc/cp/class.c | 3 + gcc/cp/constexpr.c | 4 + gcc/cp/parser.c | 24 ++++++ gcc/cp/pt.c | 4 +- gcc/cp/typeck.c | 46 ++++++++++- gcc/doc/invoke.texi | 8 ++ gcc/flag-types.h | 3 +- gcc/fold-const.c | 13 +++- gcc/gimplify.c | 7 ++ gcc/internal-fn.c | 90 ++++++++++++++++++---- gcc/internal-fn.def | 3 + gcc/opts.c | 1 + gcc/testsuite/c-c++-common/isan/liveadder.c | 15 ++++ gcc/testsuite/c-c++-common/isan/miniperl.c | 13 ++++ gcc/testsuite/c-c++-common/isan/overflow-add.c | 51 ++++++++++++ gcc/testsuite/c-c++-common/isan/overflow-mul.c | 37 +++++++++ gcc/testsuite/c-c++-common/isan/overflow-sub.c | 50 ++++++++++++ .../c-c++-common/isan/static-initializer.c | 11 +++ gcc/testsuite/g++.dg/isan/cleanup.C | 11 +++ gcc/testsuite/g++.dg/isan/constexpr.C | 8 ++ gcc/testsuite/g++.dg/isan/isan.exp | 34 ++++++++ gcc/testsuite/gcc.dg/isan/false_positive.c | 19 +++++ gcc/testsuite/gcc.dg/isan/isan.exp | 36 +++++++++ gcc/tree-ssa-reassoc.c | 2 + gcc/tree.h | 3 +- gcc/ubsan.c | 12 ++- 34 files changed, 700 insertions(+), 40 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/isan/liveadder.c create mode 100644 gcc/testsuite/c-c++-common/isan/miniperl.c create mode 100644 gcc/testsuite/c-c++-common/isan/overflow-add.c create mode 100644 gcc/testsuite/c-c++-common/isan/overflow-mul.c create mode 100644 gcc/testsuite/c-c++-common/isan/overflow-sub.c create mode 100644 gcc/testsuite/c-c++-common/isan/static-initializer.c create mode 100644 gcc/testsuite/g++.dg/isan/cleanup.C create mode 100644 gcc/testsuite/g++.dg/isan/constexpr.C create mode 100644 gcc/testsuite/g++.dg/isan/isan.exp create mode 100644 gcc/testsuite/gcc.dg/isan/false_positive.c create mode 100644 gcc/testsuite/gcc.dg/isan/isan.exp diff --git a/gcc/builtins.c b/gcc/builtins.c index bf66327..4ccd052 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -6771,6 +6771,7 @@ builtin_mathfn_code (const_tree t) const_call_expr_arg_iterator iter; if (TREE_CODE (t) != CALL_EXPR + || !CALL_EXPR_FN (t) || TREE_CODE (CALL_EXPR_FN (t)) != ADDR_EXPR) return END_BUILTINS; diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c index c6eecaf..22420bb 100644 --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -4249,10 +4249,18 @@ pointer_int_sum (location_t loc, enum tree_code resultcode, tree ptrop, tree intop, bool complain) { tree size_exp, ret; + HOST_WIDE_INT op1 = 0; /* The result is a pointer of the same type that is being added. */ tree result_type = TREE_TYPE (ptrop); + if (flag_sanitize & SANITIZE_UI_OVERFLOW) + { + /* First, try to avoid FPs if INTOP is negative constant. */ + if (tree_fits_shwi_p (intop)) + op1 = tree_to_shwi (intop); + } + if (TREE_CODE (TREE_TYPE (result_type)) == VOID_TYPE) { if (complain && warn_pointer_arith) @@ -4330,11 +4338,34 @@ pointer_int_sum (location_t loc, enum tree_code resultcode, intop = wide_int_to_tree (TREE_TYPE (intop), intop); } - /* Create the sum or difference. */ - if (resultcode == MINUS_EXPR) - intop = fold_build1_loc (loc, NEGATE_EXPR, sizetype, intop); + if (flag_sanitize & SANITIZE_UI_OVERFLOW) + { + tree tmp = NULL_TREE; + /* First, try to avoid FPs if INTOP is negative constant. */ + if (op1 != 0) + { + if (op1 < 0) + { + resultcode = (resultcode == PLUS_EXPR ? MINUS_EXPR : PLUS_EXPR); + op1 = -op1; + } + tmp = build_int_cst (sizetype, op1); + intop = build_binary_op (loc, MULT_EXPR, tmp, + convert (TREE_TYPE (intop), size_exp), 1); + } + tmp = build_binary_op (loc, resultcode, + convert (sizetype, ptrop), + convert (long_integer_type_node, intop), 1); + ret = convert (result_type, tmp); + } + else + { + /* Create the sum or difference. */ + if (resultcode == MINUS_EXPR) + intop = fold_build1_loc (loc, NEGATE_EXPR, sizetype, intop); - ret = fold_build_pointer_plus_loc (loc, ptrop, intop); + ret = fold_build_pointer_plus_loc (loc, ptrop, intop); + } fold_undefer_and_ignore_overflow_warnings (); @@ -9732,7 +9763,9 @@ check_function_arguments_recurse (void (*callback) if (TREE_CODE (param) == CALL_EXPR) { - tree type = TREE_TYPE (TREE_TYPE (CALL_EXPR_FN (param))); + tree type + = CALL_EXPR_FN (param) ? TREE_TYPE (TREE_TYPE (CALL_EXPR_FN (param))) + : TREE_TYPE (param); tree attrs; bool found_format_arg = false; diff --git a/gcc/c-family/c-ubsan.c b/gcc/c-family/c-ubsan.c index 4022bdf..82ecc5b 100644 --- a/gcc/c-family/c-ubsan.c +++ b/gcc/c-family/c-ubsan.c @@ -456,3 +456,90 @@ ubsan_maybe_instrument_member_call (tree stmt, bool is_ctor) if (op) CALL_EXPR_ARG (stmt, 0) = op; } + +/* Perform the unsigned and signed integer instrumentation for binary + operation CODE. LOC is the source location, RESULT_TYPE is computed type + for result of expression, OP0 and OP1 are corresponding operands. + UNS0_P and UNS1_P are bool flags indicating whether original OP0 and OP1 + were signed or unsigned. */ + +tree +isan_maybe_instrument_ui (location_t loc, enum tree_code code, + tree result_type, tree op0, tree op1, + int uns0_p, int uns1_p) +{ + tree op0type = TREE_TYPE (op0); + tree op1type = TREE_TYPE (op1); + + tree op0_uns_p = build_int_cst (integer_type_node, uns0_p); + tree op1_uns_p = build_int_cst (integer_type_node, uns1_p); + tree res_uns_p = build_int_cst (integer_type_node, + TYPE_UNSIGNED (result_type)); + + /* TODO: instrument even two constants. We don't do it right now to avoid + nasty errors in array bounds computations such as A[1 + 2 * sizeof (int)]. + */ + if ((TREE_CODE (op0) == INTEGER_CST) && (TREE_CODE (op1) == INTEGER_CST)) + return NULL_TREE; + + /* TODO: instrument for different precision modes. */ + if ((TYPE_PRECISION (op0type) != TYPE_PRECISION (result_type)) + || (TYPE_PRECISION (op1type) != TYPE_PRECISION (result_type))) + return NULL_TREE; + + /* If both operands are signed, don't instrument anything here. + Also punt on bit-fields. */ + if (!INTEGRAL_TYPE_P (op0type) || !INTEGRAL_TYPE_P (op1type) + || (!TYPE_OVERFLOW_WRAPS (op0type) && !TYPE_OVERFLOW_WRAPS (op1type)) + || GET_MODE_BITSIZE (TYPE_MODE (op0type)) != TYPE_PRECISION (op0type) + || GET_MODE_BITSIZE (TYPE_MODE (op1type)) != TYPE_PRECISION (op1type)) + return NULL_TREE; + + switch (code) + { + case MULT_EXPR: + case MINUS_EXPR: + case PLUS_EXPR: + /* Transform + i = u {+,-,*} 5; + into + i = ISAN_CHECK_{ADD,SUB,MUL} (u, 5); */ + return build_call_expr_internal_loc (loc, code == PLUS_EXPR + ? IFN_ISAN_CHECK_ADD + : code == MINUS_EXPR + ? IFN_ISAN_CHECK_SUB + : IFN_ISAN_CHECK_MUL, + result_type, 5, op0, op1, + op0_uns_p, op1_uns_p, + res_uns_p); + default: + break; + } + return NULL_TREE; +} + +/* Return TRUE iff FN is ISAN_CHECK_{ADD,SUB,MUL} internal function. */ + +bool +isan_internal_fn_p (tree fn) +{ + if (!fn || TREE_CODE (fn) != CALL_EXPR) + return false; + + enum internal_fn ifn = CALL_EXPR_IFN (fn); + return ifn == IFN_ISAN_CHECK_ADD || ifn == IFN_ISAN_CHECK_SUB + ||ifn == IFN_ISAN_CHECK_MUL; +} + +/* Change fourth (RES_UNS_P) parameter of ISAN_CHECK_{ADD,SUB,MUL} function + to UNS_P value. This is an extremely hacky way to avoid false psoitive + reports, because in many cases we simply cannot set RES_UNS_P value in + isan_maybe_instrument_ui correctly since we dont' see LHS and its type. */ + +void +isan_maybe_change_ifn_sign_arg (tree *call, int uns_p) +{ + tree *opp = &CALL_EXPR_ARG (*call, 4); + tree op_new = build_int_cst (integer_type_node, uns_p); + *opp = op_new; +} diff --git a/gcc/c-family/c-ubsan.h b/gcc/c-family/c-ubsan.h index 30d4f97..a8202c0 100644 --- a/gcc/c-family/c-ubsan.h +++ b/gcc/c-family/c-ubsan.h @@ -23,6 +23,8 @@ along with GCC; see the file COPYING3. If not see extern tree ubsan_instrument_division (location_t, tree, tree); extern tree ubsan_instrument_shift (location_t, enum tree_code, tree, tree); +extern tree isan_maybe_instrument_ui (location_t, enum tree_code, + tree, tree, tree, int, int); extern tree ubsan_instrument_vla (location_t, tree); extern tree ubsan_instrument_return (location_t); extern tree ubsan_instrument_bounds (location_t, tree, tree *, bool); @@ -33,5 +35,7 @@ extern void ubsan_maybe_instrument_member_call (tree, bool); /* Declare this here as well as in ubsan.h. */ extern bool do_ubsan_in_current_function (void); +extern bool isan_internal_fn_p (tree fn); +extern void isan_maybe_change_ifn_sign_arg (tree *, int); #endif /* GCC_C_UBSAN_H */ diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c index c9eb8dd..35f383e 100644 --- a/gcc/c/c-parser.c +++ b/gcc/c/c-parser.c @@ -49,6 +49,7 @@ along with GCC; see the file COPYING3. If not see #include "varasm.h" #include "trans-mem.h" #include "c-family/c-pragma.h" +#include "c-family/c-ubsan.h" #include "c-lang.h" #include "c-family/c-objc.h" #include "plugin.h" @@ -262,6 +263,8 @@ struct GTY(()) c_parser { static GTY (()) c_parser *the_parser; +bool processing_static_variable; + /* Read in and lex a single token, storing it in *TOKEN. */ static void @@ -1927,6 +1930,10 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, its initializer is parsed. */ d = start_decl (declarator, specs, true, chainon (postfix_attrs, all_prefix_attrs)); + + if (d && TREE_STATIC (d)) + processing_static_variable = true; + if (!d) d = error_mark_node; if (omp_declare_simd_clauses.exists () @@ -1936,7 +1943,12 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, start_init (d, asm_name, global_bindings_p ()); init_loc = c_parser_peek_token (parser)->location; init = c_parser_initializer (parser); + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (init.value)) + isan_maybe_change_ifn_sign_arg (&init.value, + TYPE_UNSIGNED (TREE_TYPE (d))); finish_init (); + processing_static_variable = false; } if (oacc_routine_clauses) c_finish_oacc_routine (parser, d, oacc_routine_clauses, @@ -5212,6 +5224,12 @@ c_parser_statement_after_labels (c_parser *parser, bool *if_p, location_t xloc = c_parser_peek_token (parser)->location; struct c_expr expr = c_parser_expression_conv (parser); mark_exp_read (expr.value); + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (expr.value)) + { + int uns_p = TYPE_UNSIGNED (TREE_TYPE (DECL_RESULT (current_function_decl))); + isan_maybe_change_ifn_sign_arg (&expr.value, uns_p); + } stmt = c_finish_return (EXPR_LOC_OR_LOC (expr.value, xloc), expr.value, expr.original_type); goto expect_semicolon; diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c index 2bea816..5793a4a 100644 --- a/gcc/c/c-typeck.c +++ b/gcc/c/c-typeck.c @@ -72,6 +72,8 @@ int in_typeof; if expr.original_code == SIZEOF_EXPR. */ tree c_last_sizeof_arg; +extern bool processing_static_variable; + /* Nonzero if we might need to print a "missing braces around initializer" message within this initializer. */ static int found_missing_braces; @@ -3020,6 +3022,21 @@ build_function_call_vec (location_t loc, vec arg_loc, if (nargs < 0) return error_mark_node; + for (int i = 0; i < nargs; ++i) + { + tree tmp = (*params)[i]; + if (TREE_CODE (tmp) == NOP_EXPR) + { + int real_type_unsigned_p = TYPE_UNSIGNED (TREE_TYPE (tmp)); + tree arg = TREE_OPERAND (tmp, 0); + if (isan_internal_fn_p (arg)) + { + tree *arg_p = &TREE_OPERAND ((*params)[i], 0); + isan_maybe_change_ifn_sign_arg (arg_p, real_type_unsigned_p); + } + } + } + /* Check that the function is called through a compatible prototype. If it is not, warn. */ if (CONVERT_EXPR_P (function) @@ -5570,6 +5587,13 @@ build_modify_expr (location_t location, tree lhs, tree lhs_origtype, newrhs = rhs; + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (rhs)) + { + tree lhs_type = lhs_origtype ? lhs_origtype : TREE_TYPE (lhs); + isan_maybe_change_ifn_sign_arg (&newrhs, TYPE_UNSIGNED (lhs_type)); + } + if (TREE_CODE (lhs) == C_MAYBE_CONST_EXPR) { tree inner = build_modify_expr (location, C_MAYBE_CONST_EXPR_EXPR (lhs), @@ -5607,6 +5631,12 @@ build_modify_expr (location_t location, tree lhs, tree lhs_origtype, } newrhs = build_binary_op (location, modifycode, lhs, newrhs, 1); + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (newrhs)) + { + tree lhs_type = lhs_origtype ? lhs_origtype : TREE_TYPE (lhs); + isan_maybe_change_ifn_sign_arg (&newrhs, TYPE_UNSIGNED (lhs_type)); + } /* The original type of the right hand side is no longer meaningful. */ @@ -10577,6 +10607,10 @@ build_binary_op (location_t location, enum tree_code code, /* Remember whether we're doing << or >>. */ bool doing_shift = false; + bool doing_plus = false; + bool doing_minus = false; + bool doing_mul = false; + /* Tree holding instrumentation expression. */ tree instrument_expr = NULL; @@ -10757,7 +10791,10 @@ build_binary_op (location_t location, enum tree_code code, goto return_build_binary_op; } else - common = 1; + { + doing_plus = true; + common = 1; + } break; case MINUS_EXPR: @@ -10776,10 +10813,14 @@ build_binary_op (location_t location, enum tree_code code, goto return_build_binary_op; } else - common = 1; + { + doing_minus = true; + common = 1; + } break; case MULT_EXPR: + doing_mul = true; common = 1; break; @@ -11607,6 +11648,20 @@ build_binary_op (location_t location, enum tree_code code, instrument_expr = ubsan_instrument_shift (location, code, op0, op1); } + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && (doing_plus || doing_minus || doing_mul) + && !processing_static_variable) + { + op0 = c_save_expr (op0); + op1 = c_save_expr (op1); + op0 = c_fully_fold (op0, false, NULL); + op1 = c_fully_fold (op1, false, NULL); + instrument_expr = isan_maybe_instrument_ui (location, code, + result_type, op0, op1, + TYPE_UNSIGNED (orig_type0), + TYPE_UNSIGNED (orig_type1)); + } + /* Treat expressions in initializers specially as they can't trap. */ if (int_const_or_overflow) ret = (require_constant_value @@ -11632,9 +11687,18 @@ build_binary_op (location_t location, enum tree_code code, protected_set_expr_location (ret, location); if (instrument_expr != NULL) - ret = fold_build2 (COMPOUND_EXPR, TREE_TYPE (ret), - instrument_expr, ret); - + { + if (flag_sanitize & SANITIZE_UI_OVERFLOW) + { + /* Inherit side effects. TODO: what another attributes should be + inherited? */ + TREE_SIDE_EFFECTS (instrument_expr) = TREE_SIDE_EFFECTS (ret); + ret = instrument_expr; + } + else + ret = fold_build2 (COMPOUND_EXPR, TREE_TYPE (ret), + instrument_expr, ret); + } return ret; } diff --git a/gcc/calls.c b/gcc/calls.c index 8de22f3..a39fc86 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -795,6 +795,10 @@ call_expr_flags (const_tree t) int flags; tree decl = get_callee_fndecl (t); + /* We may have UBSan internal functions here. */ + if (!CALL_EXPR_FN (t)) + return ECF_CONST | ECF_LEAF | ECF_NOTHROW; + if (decl) flags = flags_from_decl_or_type (decl); else if (CALL_EXPR_FN (t) == NULL_TREE) diff --git a/gcc/cp/call.c b/gcc/cp/call.c index a7d35b9..cbc3ec6 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -38,6 +38,7 @@ along with GCC; see the file COPYING3. If not see #include "convert.h" #include "langhooks.h" #include "c-family/c-objc.h" +#include "c-family/c-ubsan.h" #include "internal-fn.h" /* The various kinds of conversion. */ diff --git a/gcc/cp/class.c b/gcc/cp/class.c index 35deb1e..ceff3ef 100644 --- a/gcc/cp/class.c +++ b/gcc/cp/class.c @@ -36,6 +36,7 @@ along with GCC; see the file COPYING3. If not see #include "dumpfile.h" #include "gimplify.h" #include "intl.h" +#include "c-family/c-ubsan.h" /* The number of nested classes being processed. If we are not in the scope of any class, this is zero. */ @@ -7497,6 +7498,8 @@ fixed_type_or_null (tree instance, int *nonnull, int *cdtorp) return RECUR (TREE_OPERAND (instance, 0)); case CALL_EXPR: + if (isan_internal_fn_p (instance)) + return RECUR (TREE_OPERAND (instance, 0)); /* This is a call to a constructor, hence it's never zero. */ if (TREE_HAS_CONSTRUCTOR (instance)) { diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index f9c4ead..cff531e 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -4730,6 +4730,10 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, case IFN_UBSAN_BOUNDS: case IFN_UBSAN_VPTR: return true; + case IFN_ISAN_CHECK_ADD: + case IFN_ISAN_CHECK_SUB: + case IFN_ISAN_CHECK_MUL: + return false; default: break; } diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 187fe0e..bb2d371 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see #include "intl.h" #include "decl.h" #include "c-family/c-objc.h" +#include "c-family/c-ubsan.h" #include "plugin.h" #include "tree-pretty-print.h" #include "parser.h" @@ -9083,6 +9084,14 @@ cp_parser_assignment_expression (cp_parser* parser, cp_id_kind * pidk, cp_expr rhs = cp_parser_initializer_clause (parser, &non_constant_p); + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (rhs)) + { + int uns_p = TYPE_UNSIGNED (TREE_TYPE (expr)); + tree rhs_value = rhs.get_value(); + isan_maybe_change_ifn_sign_arg (&rhs_value, uns_p); + } + if (BRACE_ENCLOSED_INITIALIZER_P (rhs)) maybe_warn_cpp0x (CPP0X_INITIALIZER_LISTS); @@ -11834,6 +11843,15 @@ cp_parser_jump_statement (cp_parser* parser) /* If the next token is a `;', then there is no expression. */ expr = NULL_TREE; + + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (expr)) + { + int uns_p = TYPE_UNSIGNED (TREE_TYPE + (DECL_RESULT (current_function_decl))); + isan_maybe_change_ifn_sign_arg (&expr, uns_p); + } + /* Build the return-statement. */ statement = finish_return_stmt (expr); /* Look for the final `;'. */ @@ -18677,6 +18695,12 @@ cp_parser_init_declarator (cp_parser* parser, finish_lambda_scope (); if (initializer == error_mark_node) cp_parser_skip_to_end_of_statement (parser); + else if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (initializer)) + { + int uns_p = TYPE_UNSIGNED (TREE_TYPE (decl)); + isan_maybe_change_ifn_sign_arg (&initializer, uns_p); + } } } diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index db403c8..a98a5dc 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -22860,6 +22860,8 @@ value_dependent_expression_p (tree expression) case CALL_EXPR: { + if (!CALL_EXPR_FN (expression)) + return false; tree fn = get_callee_fndecl (expression); int i, nargs; if (!fn && value_dependent_expression_p (CALL_EXPR_FN (expression))) @@ -23224,7 +23226,7 @@ instantiation_dependent_r (tree *tp, int *walk_subtrees, case CALL_EXPR: /* Treat calls to function concepts as dependent. */ - if (function_concept_check_p (*tp)) + if (CALL_EXPR_FN (*tp) && function_concept_check_p (*tp)) return *tp; break; diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index 029d0ab..b5a9dd0 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -4060,6 +4060,9 @@ cp_build_binary_op (location_t location, tree result, result_ovl; tree orig_type = NULL; + tree orig_type0 = TREE_TYPE (orig_op0); + tree orig_type1 = TREE_TYPE (orig_op1); + /* Nonzero if this is an operation like MIN or MAX which can safely be computed in short if both args are promoted shorts. Also implies COMMON. @@ -4089,6 +4092,10 @@ cp_build_binary_op (location_t location, /* Remember whether we're doing << or >>. */ bool doing_shift = false; + bool doing_plus = false; + bool doing_minus = false; + bool doing_mul = false; + /* Tree holding instrumentation expression. */ tree instrument_expr = NULL; @@ -4231,6 +4238,7 @@ cp_build_binary_op (location_t location, else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE)) { common = 1; + doing_minus = 1; break; } /* The pointer - int case is just like pointer + int; fall @@ -4254,10 +4262,12 @@ cp_build_binary_op (location_t location, complain); } common = 1; + doing_plus = 1; break; case MULT_EXPR: common = 1; + doing_mul = 1; break; case TRUNC_DIV_EXPR: @@ -5153,13 +5163,37 @@ cp_build_binary_op (location_t location, instrument_expr = ubsan_instrument_shift (location, code, op0, op1); } + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && (doing_plus || doing_minus || doing_mul) + && !processing_template_decl) + { + op0 = cp_save_expr (op0); + op1 = cp_save_expr (op1); + op0 = fold_non_dependent_expr (op0); + op1 = fold_non_dependent_expr (op1); + instrument_expr = isan_maybe_instrument_ui (location, code, + result_type, op0, op1, + TYPE_UNSIGNED (orig_type0), + TYPE_UNSIGNED (orig_type1)); + } + result = build2_loc (location, resultcode, build_type, op0, op1); if (final_type != 0) result = cp_convert (final_type, result, complain); if (instrument_expr != NULL) - result = build2 (COMPOUND_EXPR, TREE_TYPE (result), - instrument_expr, result); + { + if (flag_sanitize & SANITIZE_UI_OVERFLOW) + { + /* Inherit side effects. TODO: what another attributes should be + inherited? */ + TREE_SIDE_EFFECTS (instrument_expr) = TREE_SIDE_EFFECTS (result); + result = instrument_expr; + } + else + result = fold_build2 (COMPOUND_EXPR, TREE_TYPE (result), + instrument_expr, result); + } if (!processing_template_decl) { @@ -7672,6 +7706,14 @@ cp_build_modify_expr (tree lhs, enum tree_code modifycode, tree rhs, newrhs = cp_build_binary_op (input_location, modifycode, lhs, rhs, complain); + + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) + && isan_internal_fn_p (newrhs)) + { + int uns_p = TYPE_UNSIGNED (TREE_TYPE (lhs)); + isan_maybe_change_ifn_sign_arg (&newrhs, uns_p); + } + if (newrhs == error_mark_node) { if (complain & tf_error) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index f2f34e8..338b326 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -9856,6 +9856,14 @@ signed char a = SCHAR_MAX; a++; @end smallexample +@item -fsanitize=unsigned-integer-overflow +@opindex fsanitize=unsigned-integer-overflow +This option enables unsigned and mixed (signed/unsigned) integer overflow +checking. We check that the result of @code{+}, @code{*}, and binary @code{-} +does not overflow in the unsigned or mixed arithmetics. These checks can +trigger false positive warnings on code that intentionally relies on overflows, +e.g. when computing hash codes. + @item -fsanitize=bounds @opindex fsanitize=bounds This option enables instrumentation of array bounds. Various out of bounds diff --git a/gcc/flag-types.h b/gcc/flag-types.h index 8201676..18cf0a3 100644 --- a/gcc/flag-types.h +++ b/gcc/flag-types.h @@ -253,6 +253,7 @@ enum sanitize_code { SANITIZE_OBJECT_SIZE = 1UL << 20, SANITIZE_VPTR = 1UL << 21, SANITIZE_BOUNDS_STRICT = 1UL << 22, + SANITIZE_UI_OVERFLOW = 1 << 23, SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN | SANITIZE_SI_OVERFLOW | SANITIZE_BOOL | SANITIZE_ENUM @@ -261,7 +262,7 @@ enum sanitize_code { | SANITIZE_RETURNS_NONNULL_ATTRIBUTE | SANITIZE_OBJECT_SIZE | SANITIZE_VPTR, SANITIZE_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST - | SANITIZE_BOUNDS_STRICT + | SANITIZE_BOUNDS_STRICT | SANITIZE_UI_OVERFLOW }; /* flag_vtable_verify initialization levels. */ diff --git a/gcc/fold-const.c b/gcc/fold-const.c index 46f1ac1..17ff640 100644 --- a/gcc/fold-const.c +++ b/gcc/fold-const.c @@ -386,7 +386,8 @@ negate_expr_p (tree t) switch (TREE_CODE (t)) { case INTEGER_CST: - if (INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_WRAPS (type)) + if (INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_WRAPS (type) + && !TYPE_OVERFLOW_SANITIZED (type)) return true; /* Check that -CST will not overflow type. */ @@ -549,8 +550,8 @@ fold_negate_expr (location_t loc, tree t) if (TREE_OVERFLOW (tem) == TREE_OVERFLOW (t) || (ANY_INTEGRAL_TYPE_P (type) && !TYPE_OVERFLOW_TRAPS (type) - && TYPE_OVERFLOW_WRAPS (type)) - || (flag_sanitize & SANITIZE_SI_OVERFLOW) == 0) + && !TYPE_OVERFLOW_SANITIZED (type)) + || (flag_sanitize & (SANITIZE_SI_OVERFLOW | SANITIZE_UI_OVERFLOW)) == 0) return tem; break; @@ -9966,7 +9967,11 @@ fold_binary_loc (location_t loc, return tem; } - goto associate; + /* Associate if we don't sanitize unsigned integer overflows. */ + if ((flag_sanitize & SANITIZE_UI_OVERFLOW) == 0) + goto associate; + + return NULL_TREE; case MULT_EXPR: if (! FLOAT_TYPE_P (type)) diff --git a/gcc/gimplify.c b/gcc/gimplify.c index 22e053a..ee6d2b5 100644 --- a/gcc/gimplify.c +++ b/gcc/gimplify.c @@ -2371,6 +2371,13 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value) enum internal_fn ifn = CALL_EXPR_IFN (*expr_p); auto_vec vargs (nargs); + /* Ugh, sometimes FE can optimize conditional thus it doesn't + depend on ISAN_*_CHECK return value. Don't fold the check in this + case, but we'll probably want introduce temporary here. */ + if (ifn == IFN_ISAN_CHECK_ADD || ifn == IFN_ISAN_CHECK_SUB + || ifn == IFN_ISAN_CHECK_MUL) + return GS_ALL_DONE; + for (i = 0; i < nargs; i++) { gimplify_arg (&CALL_EXPR_ARG (*expr_p, i), pre_p, diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c index f6805d0..1523a91 100644 --- a/gcc/internal-fn.c +++ b/gcc/internal-fn.c @@ -463,9 +463,8 @@ expand_addsub_overflow (location_t loc, tree_code code, tree lhs, int prec = GET_MODE_PRECISION (mode); rtx sgn = immed_wide_int_const (wi::min_value (prec, SIGNED), mode); bool do_xor = false; - - if (is_ubsan) - gcc_assert (!unsr_p && !uns0_p && !uns1_p); + tree orig_arg0 = arg0; + tree orig_arg1 = arg1; if (lhs) { @@ -853,8 +852,8 @@ expand_addsub_overflow (location_t loc, tree_code code, tree lhs, { /* Expand the ubsan builtin call. */ push_temp_slots (); - fn = ubsan_build_overflow_builtin (code, loc, TREE_TYPE (arg0), - arg0, arg1); + fn = ubsan_build_overflow_builtin (code, loc, TREE_TYPE (lhs), + orig_arg0, orig_arg1); expand_normal (fn); pop_temp_slots (); do_pending_stack_adjust (); @@ -868,7 +867,12 @@ expand_addsub_overflow (location_t loc, tree_code code, tree lhs, if (lhs) { if (is_ubsan) - expand_ubsan_result_store (target, res); + { + if (do_xor) + res = expand_binop (mode, add_optab, res, sgn, NULL_RTX, false, + OPTAB_LIB_WIDEN); + expand_ubsan_result_store (target, res); + } else { if (do_xor) @@ -981,6 +985,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, rtx target = NULL_RTX; signop sign; enum insn_code icode; + tree orig_arg0 = arg0; + tree orig_arg1 = arg1; done_label = gen_label_rtx (); do_error = gen_label_rtx (); @@ -998,9 +1004,6 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, write_complex_part (target, const0_rtx, true); } - if (is_ubsan) - gcc_assert (!unsr_p && !uns0_p && !uns1_p); - /* We assume both operands and result have the same precision here (GET_MODE_BITSIZE (mode)), S stands for signed type with that precision, U for unsigned type with that precision, @@ -1081,7 +1084,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, NULL, do_main_label, PROB_VERY_LIKELY); do_compare_rtx_and_jump (op1, const0_rtx, EQ, true, mode, NULL_RTX, NULL, do_main_label, PROB_VERY_LIKELY); - write_complex_part (target, const1_rtx, true); + if (!is_ubsan) + write_complex_part (target, const1_rtx, true); emit_label (do_main_label); goto do_main; default: @@ -1212,7 +1216,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, is, thus we can keep do_main code oring in overflow as is. */ do_compare_rtx_and_jump (tem, const0_rtx, EQ, true, mode, NULL_RTX, NULL, do_main_label, PROB_VERY_LIKELY); - write_complex_part (target, const1_rtx, true); + if (!is_ubsan) + write_complex_part (target, const1_rtx, true); emit_label (do_main_label); goto do_main; default: @@ -1609,8 +1614,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, { /* Expand the ubsan builtin call. */ push_temp_slots (); - fn = ubsan_build_overflow_builtin (MULT_EXPR, loc, TREE_TYPE (arg0), - arg0, arg1); + fn = ubsan_build_overflow_builtin (MULT_EXPR, loc, TREE_TYPE (lhs), + orig_arg0, orig_arg1); expand_normal (fn); pop_temp_slots (); do_pending_stack_adjust (); @@ -1627,7 +1632,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, rtx_code_label *all_done_label = gen_label_rtx (); do_compare_rtx_and_jump (res, const0_rtx, GE, false, mode, NULL_RTX, NULL, all_done_label, PROB_VERY_LIKELY); - write_complex_part (target, const1_rtx, true); + if (!is_ubsan) + write_complex_part (target, const1_rtx, true); emit_label (all_done_label); } @@ -1638,7 +1644,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, rtx_code_label *set_noovf = gen_label_rtx (); do_compare_rtx_and_jump (op1, const0_rtx, GE, false, mode, NULL_RTX, NULL, all_done_label, PROB_VERY_LIKELY); - write_complex_part (target, const1_rtx, true); + if (!is_ubsan) + write_complex_part (target, const1_rtx, true); do_compare_rtx_and_jump (op0, const0_rtx, EQ, true, mode, NULL_RTX, NULL, set_noovf, PROB_VERY_LIKELY); do_compare_rtx_and_jump (op0, constm1_rtx, NE, true, mode, NULL_RTX, @@ -1646,7 +1653,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, do_compare_rtx_and_jump (op1, res, NE, true, mode, NULL_RTX, NULL, all_done_label, PROB_VERY_UNLIKELY); emit_label (set_noovf); - write_complex_part (target, const0_rtx, true); + if (!is_ubsan) + write_complex_part (target, const0_rtx, true); emit_label (all_done_label); } @@ -1659,6 +1667,56 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, tree arg1, } } +/* Expand ISAN_CHECK_ADD call STMT. */ + +static void +expand_ISAN_CHECK_ADD (internal_fn, gcall *stmt) +{ + location_t loc = gimple_location (stmt); + tree lhs = gimple_call_lhs (stmt); + tree arg0 = gimple_call_arg (stmt, 0); + tree arg1 = gimple_call_arg (stmt, 1); + int uns0_p = tree_to_shwi (gimple_call_arg (stmt, 2)); + int uns1_p = tree_to_shwi (gimple_call_arg (stmt, 3)); + int unsr_p = tree_to_shwi (gimple_call_arg (stmt, 4)); + expand_addsub_overflow (loc, PLUS_EXPR, lhs, arg0, arg1, + unsr_p, uns0_p, uns1_p, true); +} + +/* Expand ISAN_CHECK_SUB call STMT. */ + +static void +expand_ISAN_CHECK_SUB (internal_fn, gcall *stmt) +{ + location_t loc = gimple_location (stmt); + tree lhs = gimple_call_lhs (stmt); + tree arg0 = gimple_call_arg (stmt, 0); + tree arg1 = gimple_call_arg (stmt, 1); + int uns0_p = tree_to_shwi (gimple_call_arg (stmt, 2)); + int uns1_p = tree_to_shwi (gimple_call_arg (stmt, 3)); + int unsr_p = tree_to_shwi (gimple_call_arg (stmt, 4)); + if (integer_zerop (arg0)) + expand_neg_overflow (loc, lhs, arg1, true); + else + expand_addsub_overflow (loc, MINUS_EXPR, lhs, arg0, arg1, + unsr_p, uns0_p, uns1_p, true); +} + +/* Expand ISAN_CHECK_MUL call STMT. */ + +static void +expand_ISAN_CHECK_MUL (internal_fn, gcall *stmt) +{ + location_t loc = gimple_location (stmt); + tree lhs = gimple_call_lhs (stmt); + tree arg0 = gimple_call_arg (stmt, 0); + tree arg1 = gimple_call_arg (stmt, 1); + int uns0_p = tree_to_shwi (gimple_call_arg (stmt, 2)); + int uns1_p = tree_to_shwi (gimple_call_arg (stmt, 3)); + int unsr_p = tree_to_shwi (gimple_call_arg (stmt, 4)); + expand_mul_overflow (loc, lhs, arg0, arg1, uns0_p, uns1_p, unsr_p, true); +} + /* Expand UBSAN_CHECK_ADD call STMT. */ static void diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index a62f3e8..761576d7 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -154,6 +154,9 @@ DEF_INTERNAL_FN (UBSAN_VPTR, ECF_LEAF | ECF_NOTHROW, ".RR..") DEF_INTERNAL_FN (UBSAN_CHECK_ADD, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (UBSAN_CHECK_SUB, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (UBSAN_CHECK_MUL, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) +DEF_INTERNAL_FN (ISAN_CHECK_ADD, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) +DEF_INTERNAL_FN (ISAN_CHECK_SUB, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) +DEF_INTERNAL_FN (ISAN_CHECK_MUL, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (UBSAN_OBJECT_SIZE, ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (ABNORMAL_DISPATCHER, ECF_NORETURN, NULL) DEF_INTERNAL_FN (BUILTIN_EXPECT, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) diff --git a/gcc/opts.c b/gcc/opts.c index 0f9431a..99542cb 100644 --- a/gcc/opts.c +++ b/gcc/opts.c @@ -1454,6 +1454,7 @@ const struct sanitizer_opts_s sanitizer_opts[] = SANITIZER_OPT (return, SANITIZE_RETURN), SANITIZER_OPT (null, SANITIZE_NULL), SANITIZER_OPT (signed-integer-overflow, SANITIZE_SI_OVERFLOW), + SANITIZER_OPT (unsigned-integer-overflow, SANITIZE_UI_OVERFLOW), SANITIZER_OPT (bool, SANITIZE_BOOL), SANITIZER_OPT (enum, SANITIZE_ENUM), SANITIZER_OPT (float-divide-by-zero, SANITIZE_FLOAT_DIVIDE), diff --git a/gcc/testsuite/c-c++-common/isan/liveadder.c b/gcc/testsuite/c-c++-common/isan/liveadder.c new file mode 100644 index 0000000..d76bc22 --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/liveadder.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ + +__extension__ typedef signed long long gint64; +__extension__ typedef unsigned long long guint64; +typedef signed int gint32; +typedef unsigned int guint32; +typedef int gint; + +static void add_uint32 (guint32 *out, guint32 *in, gint bytes) +{ + gint i; + for (i = 0; i < bytes / sizeof (guint32); i++) + out[i] = ((((guint64)out[i] + (guint64)in[i]) > 1) ? 111111111 : (((guint64)out[i] + (guint64)in[i]) < 0) ? 0 : 1111111); +} diff --git a/gcc/testsuite/c-c++-common/isan/miniperl.c b/gcc/testsuite/c-c++-common/isan/miniperl.c new file mode 100644 index 0000000..1887373 --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/miniperl.c @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow -fdump-tree-gimple" } */ + +extern int *PL_tmps_stack; + +void foo (int items, int tmpsbase) { + int i = items; + while (i-- > 0) + PL_tmps_stack[--tmpsbase] |= 3; + return; +} + +/* { dg-final { scan-tree-dump-times "ISAN_CHECK_ADD" 1 "gimple" } } */ diff --git a/gcc/testsuite/c-c++-common/isan/overflow-add.c b/gcc/testsuite/c-c++-common/isan/overflow-add.c new file mode 100644 index 0000000..0003842 --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/overflow-add.c @@ -0,0 +1,51 @@ +/* { dg-do run } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ + +#include +#include + +#define CHECK_ADD(type1, type2, type3, val1, val2) \ + do { \ + volatile type1 a = val1; \ + volatile type2 b = val2; \ + volatile type3 c = a + b; \ + asm volatile ("" : : "r" (c) : "memory"); \ + } while (0) + +int main () { + + CHECK_ADD(uint32_t, uint32_t, uint32_t, UINT32_MAX, 1); + CHECK_ADD(uint32_t, uint32_t, int32_t, UINT32_MAX, 1); + CHECK_ADD(uint32_t, int32_t, uint32_t, UINT32_MAX, 1); + CHECK_ADD(uint32_t, int32_t, int32_t, UINT32_MAX, 1); + CHECK_ADD(uint32_t, uint32_t, uint32_t, INT32_MAX, 1); + CHECK_ADD(uint32_t, int32_t, int32_t, INT32_MAX, -1); + + CHECK_ADD(uint64_t, uint64_t, uint64_t, UINT64_MAX, 1); + CHECK_ADD(uint64_t, uint64_t, int64_t, UINT64_MAX, 1); + CHECK_ADD(uint64_t, int64_t, uint64_t, UINT64_MAX, 1); + CHECK_ADD(uint64_t, int64_t, int64_t, UINT64_MAX, 1); + CHECK_ADD(uint64_t, uint64_t, uint64_t, INT64_MAX, 1); + CHECK_ADD(uint64_t, int64_t, uint64_t, INT64_MAX, -1); + + CHECK_ADD(int8_t *, unsigned, int8_t *, (int8_t *) UINT64_MAX, 1); + CHECK_ADD(int32_t *, unsigned, int32_t *, (int32_t *) UINT64_MAX, 1); + CHECK_ADD(int64_t *, unsigned, int64_t *, (int64_t *) UINT64_MAX, 1); + CHECK_ADD(int8_t *, unsigned, int8_t *, (int8_t *) SIZE_MAX, 1); + CHECK_ADD(int8_t *, signed, int8_t *, (int8_t *) SIZE_MAX, -1); + + return 0; +} + + +/* { dg-output "unsigned integer overflow: 4294967295 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 4294967295 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 4294967295 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 4294967295 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 18446744073709551615 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 18446744073709551615 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 18446744073709551615 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 18446744073709551615 \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: \[0-9\]+ \\+ 1 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: \[0-9\]+ \\+ 4 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: \[0-9\]+ \\+ 8 cannot be represented in type '.*'" } */ diff --git a/gcc/testsuite/c-c++-common/isan/overflow-mul.c b/gcc/testsuite/c-c++-common/isan/overflow-mul.c new file mode 100644 index 0000000..7e29eb1 --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/overflow-mul.c @@ -0,0 +1,37 @@ +/* { dg-do run } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ + +#include + +#define CHECK_MUL(sign1, sign2, sign3, type1, type2, type3, val1, val2) \ + do { \ + volatile sign1 type1 a = val1; \ + volatile sign2 type2 b = val2; \ + volatile sign3 type3 c = a * b; \ + } while (0) + +int main () { + + CHECK_MUL(unsigned, unsigned, unsigned, int, int, int, 2, UINT_MAX); + CHECK_MUL(unsigned, unsigned, signed, int, int, int, 2, UINT_MAX); + CHECK_MUL(signed, unsigned, unsigned, int, int, int, 2, UINT_MAX); + CHECK_MUL(signed, unsigned, signed, int, int, int, 2, UINT_MAX); + CHECK_MUL(unsigned, unsigned, unsigned, int, int, int, 2, INT_MAX); + CHECK_MUL(signed, signed, signed, int, int, int, -2, INT_MAX); + + CHECK_MUL(unsigned, unsigned, unsigned, long, long, long, 2, ULONG_MAX); + CHECK_MUL(unsigned, unsigned, signed, long, long, long, 2, ULONG_MAX); + CHECK_MUL(signed, unsigned, unsigned, long, long, long, 2, ULONG_MAX); + CHECK_MUL(signed, unsigned, signed, long, long, long, 2, ULONG_MAX); + CHECK_MUL(unsigned, unsigned, unsigned, long, long, long, 2, LONG_MAX); + CHECK_MUL(signed, signed, signed, long, long, long, -2, LONG_MAX); + + return 0; +} + +/* { dg-output "unsigned integer overflow: 2 \\* 4294967295 cannot be represented in type 'unsigned int'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 2 \\* 4294967295 cannot be represented in type 'unsigned int'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 2 \\* 4294967295 cannot be represented in type 'unsigned int'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 2 \\* 18446744073709551615 cannot be represented in type 'long unsigned int'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 2 \\* 18446744073709551615 cannot be represented in type 'long unsigned int'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 2 \\* 18446744073709551615 cannot be represented in type 'long unsigned int'" } */ diff --git a/gcc/testsuite/c-c++-common/isan/overflow-sub.c b/gcc/testsuite/c-c++-common/isan/overflow-sub.c new file mode 100644 index 0000000..fe03df2 --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/overflow-sub.c @@ -0,0 +1,50 @@ +/* { dg-do run } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ + +#include +#include + +#define CHECK_SUB(type1, type2, type3, val1, val2) \ + do { \ + volatile type1 a = val1; \ + volatile type2 b = val2; \ + volatile type3 c = a - b; \ + asm volatile ("" : : "r" (c) : "memory"); \ + } while (0) + +int main () { + + CHECK_SUB(uint32_t, uint32_t, uint32_t, 1, UINT32_MAX); + CHECK_SUB(uint32_t, uint32_t, int32_t, 1, UINT32_MAX); + CHECK_SUB(int32_t, uint32_t, uint32_t, 1, UINT32_MAX); + CHECK_SUB(int32_t, uint32_t, int32_t, 1, UINT32_MAX); + CHECK_SUB(uint32_t, uint32_t, uint32_t, 1, 1); + CHECK_SUB(uint32_t, int32_t, uint32_t, 1, -1); + + CHECK_SUB(uint64_t, uint64_t, uint64_t, 1, UINT64_MAX); + CHECK_SUB(uint64_t, uint64_t, int64_t, 1, UINT64_MAX); + CHECK_SUB(int64_t, uint64_t, uint64_t, 1, UINT64_MAX); + CHECK_SUB(int64_t, uint64_t, int64_t, 1, UINT64_MAX); + CHECK_SUB(uint64_t, uint64_t, uint64_t, 1, 1); + CHECK_SUB(uint64_t, int64_t, uint64_t, 1, -1); + + CHECK_SUB(int8_t *, unsigned, int8_t *, (int8_t *) 1, 2); + CHECK_SUB(int32_t *, unsigned, int32_t *, (int32_t *) 1, 2); + CHECK_SUB(int64_t *, unsigned, int64_t *, (int64_t *) 1, 2); + CHECK_SUB(int8_t *, unsigned, int8_t *, (int8_t *) 1, 1); + CHECK_SUB(int8_t *, signed, int8_t *, (int8_t *) 1, -1); + + return 0; +} + +/* { dg-output "unsigned integer overflow: 1 \\- 4294967295 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 4294967295 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 4294967295 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 4294967295 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 18446744073709551615 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 18446744073709551615 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 18446744073709551615 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 18446744073709551615 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 2 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 8 cannot be represented in type '.*'\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*unsigned integer overflow: 1 \\- 16 cannot be represented in type '.*'" } */ diff --git a/gcc/testsuite/c-c++-common/isan/static-initializer.c b/gcc/testsuite/c-c++-common/isan/static-initializer.c new file mode 100644 index 0000000..6bca344c --- /dev/null +++ b/gcc/testsuite/c-c++-common/isan/static-initializer.c @@ -0,0 +1,11 @@ +/* { dg-do compile } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow -fdump-tree-gimple" } */ + +char a; + +void foo () { + static char *b = &a + 14; + char *c = &a + 14; +} + +/* { dg-final { scan-tree-dump "ISAN_CHECK_ADD" "gimple" } } */ diff --git a/gcc/testsuite/g++.dg/isan/cleanup.C b/gcc/testsuite/g++.dg/isan/cleanup.C new file mode 100644 index 0000000..38598f8 --- /dev/null +++ b/gcc/testsuite/g++.dg/isan/cleanup.C @@ -0,0 +1,11 @@ +/* { dg-do compile } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ + +#include + +extern void bar (); + +void foo (std::string &s) { + for (int i = 0; i < 20 - s.substr(1, 6).length(); ++i) + bar (); +} diff --git a/gcc/testsuite/g++.dg/isan/constexpr.C b/gcc/testsuite/g++.dg/isan/constexpr.C new file mode 100644 index 0000000..4896396 --- /dev/null +++ b/gcc/testsuite/g++.dg/isan/constexpr.C @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow -std=gnu++11" } */ + +#include + +constexpr int foo (int i) { + return std::__lg (i); +} diff --git a/gcc/testsuite/g++.dg/isan/isan.exp b/gcc/testsuite/g++.dg/isan/isan.exp new file mode 100644 index 0000000..d4af573 --- /dev/null +++ b/gcc/testsuite/g++.dg/isan/isan.exp @@ -0,0 +1,34 @@ +# Copyright (C) 2013-2015 Free Software Foundation, Inc. +# +# 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 +# . + +# Load support procs. +load_lib g++-dg.exp +load_lib ubsan-dg.exp + +# Initialize `dg'. +dg-init +ubsan_init + +# Main loop. +if [check_effective_target_fsanitize_undefined] { + gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.C $srcdir/c-c++-common/isan/*.c]] "" "" +} + +# All done. +ubsan_finish +dg-finish diff --git a/gcc/testsuite/gcc.dg/isan/false_positive.c b/gcc/testsuite/gcc.dg/isan/false_positive.c new file mode 100644 index 0000000..a2b245e --- /dev/null +++ b/gcc/testsuite/gcc.dg/isan/false_positive.c @@ -0,0 +1,19 @@ +/* { dg-do run } */ +/* { dg-options "-fsanitize=unsigned-integer-overflow" } */ +/* { dg-set-target-env-var UBSAN_OPTIONS "halt_on_error=1" } */ + +int foo (int c) { +}; + +int bar (unsigned a, unsigned b) { + return a - b; +} + +int main() { + unsigned a = 1; + unsigned b = 2; + + int c = a - b; + foo (a - b); + c = bar (a, b); +} diff --git a/gcc/testsuite/gcc.dg/isan/isan.exp b/gcc/testsuite/gcc.dg/isan/isan.exp new file mode 100644 index 0000000..5274afc --- /dev/null +++ b/gcc/testsuite/gcc.dg/isan/isan.exp @@ -0,0 +1,36 @@ +# Copyright (C) 2013-2015 Free Software Foundation, Inc. +# +# 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 +# . + +# GCC testsuite that uses the `dg.exp' driver. + +# Load support procs. +load_lib gcc-dg.exp +load_lib ubsan-dg.exp + +# Initialize `dg'. +dg-init +ubsan_init + +# Main loop. +if [check_effective_target_fsanitize_undefined] { + gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c $srcdir/c-c++-common/isan/*.c]] "" "" +} + +# All done. +ubsan_finish +dg-finish diff --git a/gcc/tree-ssa-reassoc.c b/gcc/tree-ssa-reassoc.c index d23dabd..8d18cf1 100644 --- a/gcc/tree-ssa-reassoc.c +++ b/gcc/tree-ssa-reassoc.c @@ -2345,6 +2345,8 @@ optimize_range_tests_diff (enum tree_code opcode, tree type, mask = fold_build1 (BIT_NOT_EXPR, type, tem1); tem1 = fold_binary (MINUS_EXPR, type, fold_convert (type, rangei->exp), lowi); + if (tem1 == NULL_TREE) + return false; tem1 = fold_build2 (BIT_AND_EXPR, type, tem1, mask); lowj = build_int_cst (type, 0); if (update_range_test (rangei, rangej, NULL, 1, opcode, ops, tem1, diff --git a/gcc/tree.h b/gcc/tree.h index cb687d2..1967097 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -843,8 +843,7 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int, /* True if an overflow is to be preserved for sanitization. */ #define TYPE_OVERFLOW_SANITIZED(TYPE) \ (INTEGRAL_TYPE_P (TYPE) \ - && !TYPE_OVERFLOW_WRAPS (TYPE) \ - && (flag_sanitize & SANITIZE_SI_OVERFLOW)) + && (flag_sanitize & (SANITIZE_SI_OVERFLOW | SANITIZE_UI_OVERFLOW))) /* True if pointer types have undefined overflow. */ #define POINTER_TYPE_OVERFLOW_UNDEFINED (flag_strict_overflow) diff --git a/gcc/ubsan.c b/gcc/ubsan.c index 7b0a3f1..144686e 100644 --- a/gcc/ubsan.c +++ b/gcc/ubsan.c @@ -1242,26 +1242,29 @@ ubsan_build_overflow_builtin (tree_code code, location_t loc, tree lhstype, ubsan_type_descriptor (lhstype), NULL_TREE, NULL_TREE); enum built_in_function fn_code; + bool uns_p = TYPE_UNSIGNED (lhstype); + unsigned sanitize_code = uns_p ? SANITIZE_SI_OVERFLOW | SANITIZE_UI_OVERFLOW + : SANITIZE_SI_OVERFLOW; switch (code) { case PLUS_EXPR: - fn_code = (flag_sanitize_recover & SANITIZE_SI_OVERFLOW) + fn_code = (flag_sanitize_recover & sanitize_code) ? BUILT_IN_UBSAN_HANDLE_ADD_OVERFLOW : BUILT_IN_UBSAN_HANDLE_ADD_OVERFLOW_ABORT; break; case MINUS_EXPR: - fn_code = (flag_sanitize_recover & SANITIZE_SI_OVERFLOW) + fn_code = (flag_sanitize_recover & sanitize_code) ? BUILT_IN_UBSAN_HANDLE_SUB_OVERFLOW : BUILT_IN_UBSAN_HANDLE_SUB_OVERFLOW_ABORT; break; case MULT_EXPR: - fn_code = (flag_sanitize_recover & SANITIZE_SI_OVERFLOW) + fn_code = (flag_sanitize_recover & sanitize_code) ? BUILT_IN_UBSAN_HANDLE_MUL_OVERFLOW : BUILT_IN_UBSAN_HANDLE_MUL_OVERFLOW_ABORT; break; case NEGATE_EXPR: - fn_code = (flag_sanitize_recover & SANITIZE_SI_OVERFLOW) + fn_code = (flag_sanitize_recover & sanitize_code) ? BUILT_IN_UBSAN_HANDLE_NEGATE_OVERFLOW : BUILT_IN_UBSAN_HANDLE_NEGATE_OVERFLOW_ABORT; break; @@ -1961,6 +1964,7 @@ public: virtual bool gate (function *) { return flag_sanitize & (SANITIZE_NULL | SANITIZE_SI_OVERFLOW + | SANITIZE_UI_OVERFLOW | SANITIZE_BOOL | SANITIZE_ENUM | SANITIZE_ALIGNMENT | SANITIZE_NONNULL_ATTRIBUTE -- 2.7.4