From: Jakub Jelinek Date: Thu, 15 Jan 2015 22:58:42 +0000 (+0100) Subject: flag-types.h (enum sanitize_code): Add SANITIZE_VPTR, include SANITIZE_VPTR in SANITI... X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=35228ac722036d6e33867d309b5bd837db4a354a;p=gcc.git flag-types.h (enum sanitize_code): Add SANITIZE_VPTR, include SANITIZE_VPTR in SANITIZE_UNDEFINED. * flag-types.h (enum sanitize_code): Add SANITIZE_VPTR, include SANITIZE_VPTR in SANITIZE_UNDEFINED. * opts.c (common_handle_option): Add -fsanitize=vptr. * sanitizer.def (BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS, BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS_ABORT): New. * ubsan.h (enum ubsan_null_ckind): Add UBSAN_DOWNCAST_POINTER, UBSAN_DOWNCAST_REFERENCE, UBSAN_UPCAST and UBSAN_CAST_TO_VBASE. (ubsan_expand_vptr_ifn): New prototype. * internal-fn.c (expand_ANNOTATE, expand_GOMP_SIMD_LANE, expand_GOMP_SIMD_VF, expand_GOMP_SIMD_LAST_LANE, expand_UBSAN_NULL, expand_UBSAN_BOUNDS, expand_UBSAN_OBJECT_SIZE, expand_ASAN_CHECK, expand_LOOP_VECTORIZED): Make argument nameless, remove ATTRIBUTE_UNUSED. (expand_UBSAN_VPTR): New function. * internal-fn.def (UBSAN_NULL, ASAN_CHECK): Use R instead of W in fn spec. (UBSAN_VPTR): New internal function. * sanopt.c (tree_map_traits): Renamed to ... (sanopt_tree_map_traits): ... this. (sanopt_tree_triplet, sanopt_tree_triplet_map_traits): New classes. (sanopt_ctx): Adjust asan_check_map type for tree_map_traits to sanopt_tree_map_traits renaming. Add vptr_check_map field. (maybe_optimize_ubsan_vptr_ifn): New function. (sanopt_optimize_walker): Handle IFN_UBSAN_VPTR. (pass_sanopt::execute): Likewise. Call sanopt_optimize even for -fsanitize=vptr. * tree-ssa-alias.c (call_may_clobber_ref_p_1): Handle certain internal calls like pure functions for aliasing, even when they have other side-effects that prevent making them ECF_PURE. * ubsan.c (ubsan_vptr_type_cache_decl): New variable. (ubsan_expand_vptr_ifn): New function. cp/ * cp-gimplify.c (cp_genericize_r): Call cp_ubsan_maybe_instrument_member_call for member calls. (cp_ubsan_check_member_access_r): New function. (cp_genericize_tree): Call cp_ubsan_instrument_member_accesses. * cp-tree.h (cp_ubsan_maybe_instrument_member_call, cp_ubsan_instrument_member_accesses, cp_ubsan_maybe_instrument_downcast, cp_ubsan_maybe_instrument_cast_to_vbase): New prototypes. * cp-ubsan.c: New file. * Make-lang.in (CXX_AND_OBJCXX_OBJS): Add cp/cp-ubsan.o. * constexpr.c (cxx_eval_call_expression): Return void_node for IFN_UBSAN_VPTR. (potential_constant_expression_1): Return true for UBSAN_NULL, UBSAN_BOUNDS and UBSAN_VPTR internal calls. * typeck.c (build_class_member_access_expr): Provide locus for COMPONENT_REFs. (build_static_cast_1): Instrument downcasts. * class.c (build_base_path): For -fsanitize=vptr and !fixed_type_p add ubsan instrumentation for virtual_access. * call.c: Include internal-fn.h. (set_flags_from_callee): Handle internal calls. gcc/testsuite/ * g++.dg/ubsan/vptr-1.C: New test. * g++.dg/ubsan/vptr-2.C: New test. * g++.dg/ubsan/vptr-3.C: New test. * g++.dg/ubsan/vptr-4.C: New test. * g++.dg/ubsan/vptr-5.C: New test. * g++.dg/ubsan/vptr-6.C: New test. * g++.dg/ubsan/vptr-7.C: New test. * g++.dg/ubsan/vptr-8.C: New test. * g++.dg/ubsan/vptr-9.C: New test. From-SVN: r219695 --- diff --git a/gcc/ChangeLog b/gcc/ChangeLog index dace3d91fbf..087bcd59f87 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,37 @@ +2015-01-15 Jakub Jelinek + + * flag-types.h (enum sanitize_code): Add SANITIZE_VPTR, + include SANITIZE_VPTR in SANITIZE_UNDEFINED. + * opts.c (common_handle_option): Add -fsanitize=vptr. + * sanitizer.def (BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS, + BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS_ABORT): New. + * ubsan.h (enum ubsan_null_ckind): Add UBSAN_DOWNCAST_POINTER, + UBSAN_DOWNCAST_REFERENCE, UBSAN_UPCAST and UBSAN_CAST_TO_VBASE. + (ubsan_expand_vptr_ifn): New prototype. + * internal-fn.c (expand_ANNOTATE, expand_GOMP_SIMD_LANE, + expand_GOMP_SIMD_VF, expand_GOMP_SIMD_LAST_LANE, expand_UBSAN_NULL, + expand_UBSAN_BOUNDS, expand_UBSAN_OBJECT_SIZE, expand_ASAN_CHECK, + expand_LOOP_VECTORIZED): Make argument nameless, remove + ATTRIBUTE_UNUSED. + (expand_UBSAN_VPTR): New function. + * internal-fn.def (UBSAN_NULL, ASAN_CHECK): Use R instead of W + in fn spec. + (UBSAN_VPTR): New internal function. + * sanopt.c (tree_map_traits): Renamed to ... + (sanopt_tree_map_traits): ... this. + (sanopt_tree_triplet, sanopt_tree_triplet_map_traits): New classes. + (sanopt_ctx): Adjust asan_check_map type for tree_map_traits + to sanopt_tree_map_traits renaming. Add vptr_check_map field. + (maybe_optimize_ubsan_vptr_ifn): New function. + (sanopt_optimize_walker): Handle IFN_UBSAN_VPTR. + (pass_sanopt::execute): Likewise. Call sanopt_optimize even for + -fsanitize=vptr. + * tree-ssa-alias.c (call_may_clobber_ref_p_1): Handle certain + internal calls like pure functions for aliasing, even when they + have other side-effects that prevent making them ECF_PURE. + * ubsan.c (ubsan_vptr_type_cache_decl): New variable. + (ubsan_expand_vptr_ifn): New function. + 2015-01-15 Vladimir Makarov PR rtl-optimization/64110 diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 9120e07eaf9..889f3c1a4d5 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,27 @@ +2015-01-15 Jakub Jelinek + + * cp-gimplify.c (cp_genericize_r): Call + cp_ubsan_maybe_instrument_member_call for member calls. + (cp_ubsan_check_member_access_r): New function. + (cp_genericize_tree): Call cp_ubsan_instrument_member_accesses. + * cp-tree.h (cp_ubsan_maybe_instrument_member_call, + cp_ubsan_instrument_member_accesses, + cp_ubsan_maybe_instrument_downcast, + cp_ubsan_maybe_instrument_cast_to_vbase): New prototypes. + * cp-ubsan.c: New file. + * Make-lang.in (CXX_AND_OBJCXX_OBJS): Add cp/cp-ubsan.o. + * constexpr.c (cxx_eval_call_expression): Return void_node + for IFN_UBSAN_VPTR. + (potential_constant_expression_1): Return true for + UBSAN_NULL, UBSAN_BOUNDS and UBSAN_VPTR internal calls. + * typeck.c (build_class_member_access_expr): Provide locus + for COMPONENT_REFs. + (build_static_cast_1): Instrument downcasts. + * class.c (build_base_path): For -fsanitize=vptr and !fixed_type_p + add ubsan instrumentation for virtual_access. + * call.c: Include internal-fn.h. + (set_flags_from_callee): Handle internal calls. + 2015-01-15 Momchil Velikov PR c++/59366 diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in index e1f44912b42..e98beb1e33e 100644 --- a/gcc/cp/Make-lang.in +++ b/gcc/cp/Make-lang.in @@ -78,7 +78,7 @@ CXX_AND_OBJCXX_OBJS = cp/call.o cp/decl.o cp/expr.o cp/pt.o cp/typeck2.o \ cp/mangle.o cp/cp-objcp-common.o cp/name-lookup.o cp/cxx-pretty-print.o \ cp/cp-cilkplus.o \ cp/cp-gimplify.o cp/cp-array-notation.o cp/lambda.o \ - cp/vtable-class-hierarchy.o cp/constexpr.o $(CXX_C_OBJS) + cp/vtable-class-hierarchy.o cp/constexpr.o cp/cp-ubsan.o $(CXX_C_OBJS) # Language-specific object files for C++. CXX_OBJS = cp/cp-lang.o c-family/stub-objc.o $(CXX_AND_OBJCXX_OBJS) diff --git a/gcc/cp/call.c b/gcc/cp/call.c index 9568c836882..f2076c67aee 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -58,6 +58,7 @@ along with GCC; see the file COPYING3. If not see #include "ipa-ref.h" #include "cgraph.h" #include "wide-int.h" +#include "internal-fn.h" /* The various kinds of conversion. */ @@ -338,13 +339,16 @@ build_call_n (tree function, int n, ...) void set_flags_from_callee (tree call) { - int nothrow; + bool nothrow; tree decl = get_callee_fndecl (call); /* We check both the decl and the type; a function may be known not to throw without being declared throw(). */ - nothrow = ((decl && TREE_NOTHROW (decl)) - || TYPE_NOTHROW_P (TREE_TYPE (TREE_TYPE (CALL_EXPR_FN (call))))); + nothrow = decl && TREE_NOTHROW (decl); + if (CALL_EXPR_FN (call)) + nothrow |= TYPE_NOTHROW_P (TREE_TYPE (TREE_TYPE (CALL_EXPR_FN (call)))); + else if (internal_fn_flags (CALL_EXPR_IFN (call)) & ECF_NOTHROW) + nothrow = true; if (!nothrow && at_function_scope_p () && cfun && cp_function_chain) cp_function_chain->can_throw = 1; diff --git a/gcc/cp/class.c b/gcc/cp/class.c index edb87fe2e0a..1273064db3a 100644 --- a/gcc/cp/class.c +++ b/gcc/cp/class.c @@ -447,10 +447,20 @@ build_base_path (enum tree_code code, v_offset = cp_build_indirect_ref (v_offset, RO_NULL, complain); } else - v_offset = build_vfield_ref (cp_build_indirect_ref (expr, RO_NULL, - complain), - TREE_TYPE (TREE_TYPE (expr))); - + { + tree t = expr; + if ((flag_sanitize & SANITIZE_VPTR) && fixed_type_p == 0) + { + t = cp_ubsan_maybe_instrument_cast_to_vbase (input_location, + probe, expr); + if (t == NULL_TREE) + t = expr; + } + v_offset = build_vfield_ref (cp_build_indirect_ref (t, RO_NULL, + complain), + TREE_TYPE (TREE_TYPE (expr))); + } + if (v_offset == error_mark_node) return error_mark_node; diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 943ecbfecb1..0d474245d36 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -1176,6 +1176,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, { case IFN_UBSAN_NULL: case IFN_UBSAN_BOUNDS: + case IFN_UBSAN_VPTR: return void_node; default: if (!ctx->quiet) @@ -3820,6 +3821,19 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, if (fun == NULL_TREE) { + if (TREE_CODE (t) == CALL_EXPR + && CALL_EXPR_FN (t) == NULL_TREE) + switch (CALL_EXPR_IFN (t)) + { + /* These should be ignored, they are optimized away from + constexpr functions. */ + case IFN_UBSAN_NULL: + case IFN_UBSAN_BOUNDS: + case IFN_UBSAN_VPTR: + return true; + default: + break; + } /* fold_call_expr can't do anything with IFN calls. */ if (flags & tf_error) error_at (EXPR_LOC_OR_LOC (t, input_location), diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c index 6136b6059f5..4233a64aefe 100644 --- a/gcc/cp/cp-gimplify.c +++ b/gcc/cp/cp-gimplify.c @@ -1197,9 +1197,11 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) *stmt_p = size_one_node; return NULL; } - else if (flag_sanitize & (SANITIZE_NULL | SANITIZE_ALIGNMENT)) + else if (flag_sanitize + & (SANITIZE_NULL | SANITIZE_ALIGNMENT | SANITIZE_VPTR)) { - if (TREE_CODE (stmt) == NOP_EXPR + if ((flag_sanitize & (SANITIZE_NULL | SANITIZE_ALIGNMENT)) + && TREE_CODE (stmt) == NOP_EXPR && TREE_CODE (TREE_TYPE (stmt)) == REFERENCE_TYPE) ubsan_maybe_instrument_reference (stmt); else if (TREE_CODE (stmt) == CALL_EXPR) @@ -1214,7 +1216,10 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) = TREE_CODE (fn) == ADDR_EXPR && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL && DECL_CONSTRUCTOR_P (TREE_OPERAND (fn, 0)); - ubsan_maybe_instrument_member_call (stmt, is_ctor); + if (flag_sanitize & (SANITIZE_NULL | SANITIZE_ALIGNMENT)) + ubsan_maybe_instrument_member_call (stmt, is_ctor); + if ((flag_sanitize & SANITIZE_VPTR) && !is_ctor) + cp_ubsan_maybe_instrument_member_call (stmt); } } } @@ -1237,6 +1242,8 @@ cp_genericize_tree (tree* t_p) cp_walk_tree (t_p, cp_genericize_r, &wtd, NULL); delete wtd.p_set; wtd.bind_expr_stack.release (); + if (flag_sanitize & SANITIZE_VPTR) + cp_ubsan_instrument_member_accesses (t_p); } /* If a function that should end with a return in non-void diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 10c63fd1b4d..1176583cef7 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -6412,6 +6412,12 @@ extern vec cx_error_context (void); /* In c-family/cilk.c */ extern bool cilk_valid_spawn (tree); +/* In cp-ubsan.c */ +extern void cp_ubsan_maybe_instrument_member_call (tree); +extern void cp_ubsan_instrument_member_accesses (tree *); +extern tree cp_ubsan_maybe_instrument_downcast (location_t, tree, tree); +extern tree cp_ubsan_maybe_instrument_cast_to_vbase (location_t, tree, tree); + /* -- end of C++ */ #endif /* ! GCC_CP_TREE_H */ diff --git a/gcc/cp/cp-ubsan.c b/gcc/cp/cp-ubsan.c new file mode 100644 index 00000000000..69e486442fe --- /dev/null +++ b/gcc/cp/cp-ubsan.c @@ -0,0 +1,302 @@ +/* UndefinedBehaviorSanitizer, undefined behavior detector. + Copyright (C) 2014 Free Software Foundation, Inc. + Contributed by Jakub Jelinek + +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 +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "hash-set.h" +#include "machmode.h" +#include "vec.h" +#include "double-int.h" +#include "input.h" +#include "alias.h" +#include "symtab.h" +#include "options.h" +#include "wide-int.h" +#include "inchash.h" +#include "tree.h" +#include "alloc-pool.h" +#include "output.h" +#include "toplev.h" +#include "ubsan.h" +#include "cp-tree.h" +#include "c-family/c-common.h" +#include "c-family/c-ubsan.h" +#include "asan.h" +#include "internal-fn.h" +#include "stor-layout.h" +#include "builtins.h" +#include "fold-const.h" +#include "stringpool.h" +#include "is-a.h" +#include "predict.h" +#include "tree-ssa-alias.h" +#include "basic-block.h" +#include "gimple-expr.h" +#include "gimple.h" +#include "ipa-ref.h" +#include "lto-streamer.h" +#include "cgraph.h" + +/* Test if we should instrument vptr access. */ + +static bool +cp_ubsan_instrument_vptr_p (tree type) +{ + if (!flag_rtti || flag_sanitize_undefined_trap_on_error) + return false; + + if (current_function_decl + && lookup_attribute ("no_sanitize_undefined", + DECL_ATTRIBUTES (current_function_decl))) + return false; + + if (type) + { + type = TYPE_MAIN_VARIANT (type); + if (!CLASS_TYPE_P (type) || !CLASSTYPE_VTABLES (type)) + return false; + } + + return true; +} + +/* Helper function for + cp_ubsan_maybe_instrument_{member_{call,access},downcast}. + Instrument vptr access. */ + +static tree +cp_ubsan_instrument_vptr (location_t loc, tree op, tree type, bool is_addr, + enum ubsan_null_ckind ckind) +{ + type = TYPE_MAIN_VARIANT (type); + const char *mangled = mangle_type_string (type); + hashval_t str_hash1 = htab_hash_string (mangled); + hashval_t str_hash2 = iterative_hash (mangled, strlen (mangled), 0); + tree str_hash = wide_int_to_tree (uint64_type_node, + wi::uhwi (((uint64_t) str_hash1 << 32) + | str_hash2, 64)); + if (!is_addr) + op = build_fold_addr_expr_loc (loc, op); + op = save_expr (op); + tree vptr = fold_build3_loc (loc, COMPONENT_REF, + TREE_TYPE (TYPE_VFIELD (type)), + build_fold_indirect_ref_loc (loc, op), + TYPE_VFIELD (type), NULL_TREE); + vptr = fold_convert_loc (loc, pointer_sized_int_node, vptr); + vptr = fold_convert_loc (loc, uint64_type_node, vptr); + if (ckind == UBSAN_DOWNCAST_POINTER) + vptr = fold_build3 (COND_EXPR, uint64_type_node, + fold_build2 (NE_EXPR, boolean_type_node, op, + build_zero_cst (TREE_TYPE (op))), + vptr, build_int_cst (uint64_type_node, 0)); + vptr = build1_loc (loc, SAVE_EXPR, uint64_type_node, vptr); + tree ti_decl = get_tinfo_decl (type); + mark_used (ti_decl); + tree ptype = build_pointer_type (type); + tree call + = build_call_expr_internal_loc (loc, IFN_UBSAN_VPTR, + void_type_node, 5, op, vptr, str_hash, + build_address (ti_decl), + build_int_cst (ptype, ckind)); + TREE_SIDE_EFFECTS (call) = 1; + return fold_build2 (COMPOUND_EXPR, TREE_TYPE (op), call, op); +} + +/* Helper function for + cp_ubsan_maybe_instrument_{member_{call,access},downcast}. + Instrument vptr access if it should be instrumented, otherwise return + NULL_TREE. */ + +static tree +cp_ubsan_maybe_instrument_vptr (location_t loc, tree op, tree type, + bool is_addr, enum ubsan_null_ckind ckind) +{ + if (!cp_ubsan_instrument_vptr_p (type)) + return NULL_TREE; + return cp_ubsan_instrument_vptr (loc, op, type, is_addr, ckind); +} + +/* Instrument a member call (but not constructor call) if needed. */ + +void +cp_ubsan_maybe_instrument_member_call (tree stmt) +{ + if (call_expr_nargs (stmt) == 0) + return; + tree *opp = &CALL_EXPR_ARG (stmt, 0); + tree op = *opp; + if (op == error_mark_node + || !POINTER_TYPE_P (TREE_TYPE (op))) + return; + while (TREE_CODE (op) == COMPOUND_EXPR) + { + opp = &TREE_OPERAND (op, 1); + op = *opp; + } + op = cp_ubsan_maybe_instrument_vptr (EXPR_LOCATION (stmt), op, + TREE_TYPE (TREE_TYPE (op)), + true, UBSAN_MEMBER_CALL); + if (op) + *opp = op; +} + +/* Data passed to cp_ubsan_check_member_access_r. */ + +struct cp_ubsan_check_member_access_data +{ + hash_set *pset; + bool is_addr; +}; + +static tree cp_ubsan_check_member_access_r (tree *, int *, void *); + +/* Instrument a member access. */ + +static bool +cp_ubsan_maybe_instrument_member_access + (tree stmt, cp_ubsan_check_member_access_data *ucmd) +{ + if (DECL_ARTIFICIAL (TREE_OPERAND (stmt, 1))) + return false; + + tree base = TREE_OPERAND (stmt, 0); + if (!cp_ubsan_instrument_vptr_p (TREE_TYPE (base))) + return false; + + cp_walk_tree (&base, cp_ubsan_check_member_access_r, ucmd, ucmd->pset); + + base = cp_ubsan_instrument_vptr (EXPR_LOCATION (stmt), base, + TREE_TYPE (base), false, + UBSAN_MEMBER_ACCESS); + TREE_OPERAND (stmt, 0) + = build_fold_indirect_ref_loc (EXPR_LOCATION (stmt), base); + return true; +} + +/* Attempt to instrument member accesses inside of the function. + cp_ubsan_maybe_instrument_member_access should be called on COMPONENT_REFs + in the GENERIC IL, but only when the field is actually accessed, not + merely when it's address is taken. Therefore we track in is_addr field + whether in the current context we are processing address taken + handled components or not. E.g. for &x->y[w->z] we want to call + cp_ubsan_maybe_instrument_member_access on *w.z COMPONENT_REF, but + not on *x.y. */ + +static tree +cp_ubsan_check_member_access_r (tree *stmt_p, int *walk_subtrees, void *data) +{ + tree stmt = *stmt_p, t; + cp_ubsan_check_member_access_data *ucmd + = (cp_ubsan_check_member_access_data *) data; + switch (TREE_CODE (stmt)) + { + case ADDR_EXPR: + t = TREE_OPERAND (stmt, 0); + while ((TREE_CODE (t) == MEM_REF || TREE_CODE (t) == INDIRECT_REF) + && TREE_CODE (TREE_OPERAND (t, 0)) == ADDR_EXPR) + t = TREE_OPERAND (TREE_OPERAND (t, 0), 0); + if (handled_component_p (t)) + { + *walk_subtrees = 0; + ucmd->is_addr = true; + cp_walk_tree (&t, cp_ubsan_check_member_access_r, + data, ucmd->pset); + ucmd->is_addr = false; + } + break; + case MEM_REF: + case INDIRECT_REF: + t = TREE_OPERAND (stmt, 0); + if (TREE_CODE (t) == ADDR_EXPR) + { + *walk_subtrees = 0; + t = TREE_OPERAND (stmt, 0); + cp_walk_tree (&t, cp_ubsan_check_member_access_r, data, ucmd->pset); + } + break; + case COMPONENT_REF: + if (!ucmd->is_addr && cp_ubsan_maybe_instrument_member_access (stmt, ucmd)) + { + *walk_subtrees = 0; + break; + } + /* FALLTHRU */ + default: + if (ucmd->is_addr && handled_component_p (stmt)) + { + int i, len = TREE_OPERAND_LENGTH (stmt); + *walk_subtrees = 0; + if (!handled_component_p (TREE_OPERAND (stmt, 0))) + ucmd->is_addr = false; + for (i = 0; i < len; i++) + { + cp_walk_tree (&TREE_OPERAND (stmt, i), + cp_ubsan_check_member_access_r, data, ucmd->pset); + ucmd->is_addr = false; + } + ucmd->is_addr = true; + } + break; + } + return NULL_TREE; +} + +/* Instrument all member accesses inside GENERIC *T_P. */ + +void +cp_ubsan_instrument_member_accesses (tree *t_p) +{ + if (cp_ubsan_instrument_vptr_p (NULL_TREE)) + { + hash_set pset; + cp_ubsan_check_member_access_data ucmd; + ucmd.pset = &pset; + ucmd.is_addr = false; + cp_walk_tree (t_p, cp_ubsan_check_member_access_r, &ucmd, &pset); + } +} + +/* Instrument downcast. */ + +tree +cp_ubsan_maybe_instrument_downcast (location_t loc, tree type, tree op) +{ + if (!POINTER_TYPE_P (type) + || !POINTER_TYPE_P (TREE_TYPE (op)) + || !CLASS_TYPE_P (TREE_TYPE (type)) + || !CLASS_TYPE_P (TREE_TYPE (TREE_TYPE (op))) + || !DERIVED_FROM_P (TREE_TYPE (TREE_TYPE (op)), TREE_TYPE (type))) + return NULL_TREE; + + return cp_ubsan_maybe_instrument_vptr (loc, op, TREE_TYPE (type), true, + TREE_CODE (type) == POINTER_TYPE + ? UBSAN_DOWNCAST_POINTER + : UBSAN_DOWNCAST_REFERENCE); +} + +/* Instrument cast to virtual base. */ + +tree +cp_ubsan_maybe_instrument_cast_to_vbase (location_t loc, tree type, tree op) +{ + return cp_ubsan_maybe_instrument_vptr (loc, op, type, true, + UBSAN_CAST_TO_VBASE); +} diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index 69e22290765..32ee78ec307 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -2425,8 +2425,8 @@ build_class_member_access_expr (tree object, tree member, member_type = cp_build_qualified_type (member_type, type_quals); } - result = build3 (COMPONENT_REF, member_type, object, member, - NULL_TREE); + result = build3_loc (input_location, COMPONENT_REF, member_type, + object, member, NULL_TREE); result = fold_if_not_in_template (result); /* Mark the expression const or volatile, as appropriate. Even @@ -6474,11 +6474,21 @@ build_static_cast_1 (tree type, tree expr, bool c_cast_p, base = lookup_base (TREE_TYPE (type), intype, c_cast_p ? ba_unique : ba_check, NULL, complain); + expr = build_address (expr); + + if (flag_sanitize & SANITIZE_VPTR) + { + tree ubsan_check + = cp_ubsan_maybe_instrument_downcast (input_location, type, expr); + if (ubsan_check) + expr = ubsan_check; + } /* Convert from "B*" to "D*". This function will check that "B" is not a virtual base of "D". */ - expr = build_base_path (MINUS_EXPR, build_address (expr), - base, /*nonnull=*/false, complain); + expr = build_base_path (MINUS_EXPR, expr, base, /*nonnull=*/false, + complain); + /* Convert the pointer to a reference -- but then remember that there are no expressions with reference type in C++. @@ -6606,7 +6616,16 @@ build_static_cast_1 (tree type, tree expr, bool c_cast_p, NULL, complain); expr = build_base_path (MINUS_EXPR, expr, base, /*nonnull=*/false, complain); - return cp_fold_convert(type, expr); + + if (flag_sanitize & SANITIZE_VPTR) + { + tree ubsan_check + = cp_ubsan_maybe_instrument_downcast (input_location, type, expr); + if (ubsan_check) + expr = ubsan_check; + } + + return cp_fold_convert (type, expr); } if ((TYPE_PTRDATAMEM_P (type) && TYPE_PTRDATAMEM_P (intype)) diff --git a/gcc/flag-types.h b/gcc/flag-types.h index 6588a7b645a..bfdce442a62 100644 --- a/gcc/flag-types.h +++ b/gcc/flag-types.h @@ -237,13 +237,14 @@ enum sanitize_code { SANITIZE_NONNULL_ATTRIBUTE = 1UL << 18, SANITIZE_RETURNS_NONNULL_ATTRIBUTE = 1UL << 19, SANITIZE_OBJECT_SIZE = 1UL << 20, + SANITIZE_VPTR = 1UL << 21, SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN | SANITIZE_SI_OVERFLOW | SANITIZE_BOOL | SANITIZE_ENUM | SANITIZE_BOUNDS | SANITIZE_ALIGNMENT | SANITIZE_NONNULL_ATTRIBUTE | SANITIZE_RETURNS_NONNULL_ATTRIBUTE - | SANITIZE_OBJECT_SIZE, + | SANITIZE_OBJECT_SIZE | SANITIZE_VPTR, SANITIZE_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST }; diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c index 0609e4a34c7..e4028250c2a 100644 --- a/gcc/internal-fn.c +++ b/gcc/internal-fn.c @@ -166,7 +166,7 @@ expand_STORE_LANES (gcall *stmt) } static void -expand_ANNOTATE (gcall *stmt ATTRIBUTE_UNUSED) +expand_ANNOTATE (gcall *) { gcc_unreachable (); } @@ -174,7 +174,7 @@ expand_ANNOTATE (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in adjust_simduid_builtins. */ static void -expand_GOMP_SIMD_LANE (gcall *stmt ATTRIBUTE_UNUSED) +expand_GOMP_SIMD_LANE (gcall *) { gcc_unreachable (); } @@ -182,7 +182,7 @@ expand_GOMP_SIMD_LANE (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in adjust_simduid_builtins. */ static void -expand_GOMP_SIMD_VF (gcall *stmt ATTRIBUTE_UNUSED) +expand_GOMP_SIMD_VF (gcall *) { gcc_unreachable (); } @@ -190,7 +190,7 @@ expand_GOMP_SIMD_VF (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in adjust_simduid_builtins. */ static void -expand_GOMP_SIMD_LAST_LANE (gcall *stmt ATTRIBUTE_UNUSED) +expand_GOMP_SIMD_LAST_LANE (gcall *) { gcc_unreachable (); } @@ -198,7 +198,7 @@ expand_GOMP_SIMD_LAST_LANE (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in the sanopt pass. */ static void -expand_UBSAN_NULL (gcall *stmt ATTRIBUTE_UNUSED) +expand_UBSAN_NULL (gcall *) { gcc_unreachable (); } @@ -206,7 +206,7 @@ expand_UBSAN_NULL (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in the sanopt pass. */ static void -expand_UBSAN_BOUNDS (gcall *stmt ATTRIBUTE_UNUSED) +expand_UBSAN_BOUNDS (gcall *) { gcc_unreachable (); } @@ -214,7 +214,7 @@ expand_UBSAN_BOUNDS (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in the sanopt pass. */ static void -expand_UBSAN_OBJECT_SIZE (gcall *stmt ATTRIBUTE_UNUSED) +expand_UBSAN_VPTR (gcall *) { gcc_unreachable (); } @@ -222,7 +222,15 @@ expand_UBSAN_OBJECT_SIZE (gcall *stmt ATTRIBUTE_UNUSED) /* This should get expanded in the sanopt pass. */ static void -expand_ASAN_CHECK (gcall *stmt ATTRIBUTE_UNUSED) +expand_UBSAN_OBJECT_SIZE (gcall *) +{ + gcc_unreachable (); +} + +/* This should get expanded in the sanopt pass. */ + +static void +expand_ASAN_CHECK (gcall *) { gcc_unreachable (); } @@ -1889,7 +1897,7 @@ expand_MUL_OVERFLOW (gcall *stmt) /* This should get folded in tree-vectorizer.c. */ static void -expand_LOOP_VECTORIZED (gcall *stmt ATTRIBUTE_UNUSED) +expand_LOOP_VECTORIZED (gcall *) { gcc_unreachable (); } diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index f7ebce34225..032ce6c909e 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -48,15 +48,16 @@ DEF_INTERNAL_FN (LOOP_VECTORIZED, ECF_NOVOPS | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (MASK_LOAD, ECF_PURE | ECF_LEAF, NULL) DEF_INTERNAL_FN (MASK_STORE, ECF_LEAF, NULL) DEF_INTERNAL_FN (ANNOTATE, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) -DEF_INTERNAL_FN (UBSAN_NULL, ECF_LEAF | ECF_NOTHROW, ".W.") +DEF_INTERNAL_FN (UBSAN_NULL, ECF_LEAF | ECF_NOTHROW, ".R.") DEF_INTERNAL_FN (UBSAN_BOUNDS, ECF_LEAF | ECF_NOTHROW, NULL) +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 (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) -DEF_INTERNAL_FN (ASAN_CHECK, ECF_TM_PURE | ECF_LEAF | ECF_NOTHROW, ".W...") +DEF_INTERNAL_FN (ASAN_CHECK, ECF_TM_PURE | ECF_LEAF | ECF_NOTHROW, ".R...") DEF_INTERNAL_FN (ADD_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (SUB_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) DEF_INTERNAL_FN (MUL_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) diff --git a/gcc/opts.c b/gcc/opts.c index 8a16116665f..305e3494972 100644 --- a/gcc/opts.c +++ b/gcc/opts.c @@ -1588,6 +1588,7 @@ common_handle_option (struct gcc_options *opts, sizeof "returns-nonnull-attribute" - 1 }, { "object-size", SANITIZE_OBJECT_SIZE, sizeof "object-size" - 1 }, + { "vptr", SANITIZE_VPTR, sizeof "vptr" - 1 }, { "all", ~0, sizeof "all" - 1 }, { NULL, 0, 0 } }; diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def index b0f423d58b0..0f189288107 100644 --- a/gcc/sanitizer.def +++ b/gcc/sanitizer.def @@ -499,3 +499,11 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN_ABORT, "__ubsan_handle_nonnull_return_abort", BT_FN_VOID_PTR, ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS, + "__ubsan_handle_dynamic_type_cache_miss", + BT_FN_VOID_PTR_PTR_PTR, + ATTR_COLD_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS_ABORT, + "__ubsan_handle_dynamic_type_cache_miss_abort", + BT_FN_VOID_PTR_PTR_PTR, + ATTR_COLD_NOTHROW_LEAF_LIST) diff --git a/gcc/sanopt.c b/gcc/sanopt.c index fa25890ef47..9689aef4f0a 100644 --- a/gcc/sanopt.c +++ b/gcc/sanopt.c @@ -108,7 +108,7 @@ maybe_get_single_definition (tree t) /* Traits class for tree hash maps below. */ -struct tree_map_traits : default_hashmap_traits +struct sanopt_tree_map_traits : default_hashmap_traits { static inline hashval_t hash (const_tree ref) { @@ -121,6 +121,63 @@ struct tree_map_traits : default_hashmap_traits } }; +/* Tree triplet for vptr_check_map. */ +struct sanopt_tree_triplet +{ + tree t1, t2, t3; +}; + +/* Traits class for tree triplet hash maps below. */ + +struct sanopt_tree_triplet_map_traits : default_hashmap_traits +{ + static inline hashval_t + hash (const sanopt_tree_triplet &ref) + { + inchash::hash hstate (0); + inchash::add_expr (ref.t1, hstate); + inchash::add_expr (ref.t2, hstate); + inchash::add_expr (ref.t3, hstate); + return hstate.end (); + } + + static inline bool + equal_keys (const sanopt_tree_triplet &ref1, const sanopt_tree_triplet &ref2) + { + return operand_equal_p (ref1.t1, ref2.t1, 0) + && operand_equal_p (ref1.t2, ref2.t2, 0) + && operand_equal_p (ref1.t3, ref2.t3, 0); + } + + template + static inline void + mark_deleted (T &e) + { + e.m_key.t1 = reinterpret_cast (1); + } + + template + static inline void + mark_empty (T &e) + { + e.m_key.t1 = NULL; + } + + template + static inline bool + is_deleted (T &e) + { + return e.m_key.t1 == (void *) 1; + } + + template + static inline bool + is_empty (T &e) + { + return e.m_key.t1 == NULL; + } +}; + /* This is used to carry various hash maps and variables used in sanopt_optimize_walker. */ @@ -132,7 +189,13 @@ struct sanopt_ctx /* This map maps a pointer (the second argument of ASAN_CHECK) to a vector of ASAN_CHECK call statements that check the access. */ - hash_map, tree_map_traits> asan_check_map; + hash_map, sanopt_tree_map_traits> asan_check_map; + + /* This map maps a tree triplet (the first, second and fourth argument + of UBSAN_VPTR) to a vector of UBSAN_VPTR call statements that check + that virtual table pointer. */ + hash_map, + sanopt_tree_triplet_map_traits> vptr_check_map; /* Number of IFN_ASAN_CHECK statements. */ int asan_num_accesses; @@ -306,6 +369,32 @@ maybe_optimize_ubsan_null_ifn (struct sanopt_ctx *ctx, gimple stmt) return remove; } +/* Optimize away redundant UBSAN_VPTR calls. The second argument + is the value loaded from the virtual table, so rely on FRE to find out + when we can actually optimize. */ + +static bool +maybe_optimize_ubsan_vptr_ifn (struct sanopt_ctx *ctx, gimple stmt) +{ + gcc_assert (gimple_call_num_args (stmt) == 5); + sanopt_tree_triplet triplet; + triplet.t1 = gimple_call_arg (stmt, 0); + triplet.t2 = gimple_call_arg (stmt, 1); + triplet.t3 = gimple_call_arg (stmt, 3); + + auto_vec &v = ctx->vptr_check_map.get_or_insert (triplet); + gimple g = maybe_get_dominating_check (v); + if (!g) + { + /* For this PTR we don't have any UBSAN_VPTR stmts recorded, so there's + nothing to optimize yet. */ + v.safe_push (stmt); + return false; + } + + return true; +} + /* Returns TRUE if ASan check of length LEN in block BB can be removed if preceded by checks in V. */ @@ -497,6 +586,9 @@ sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx) case IFN_UBSAN_NULL: remove = maybe_optimize_ubsan_null_ifn (ctx, stmt); break; + case IFN_UBSAN_VPTR: + remove = maybe_optimize_ubsan_vptr_ifn (ctx, stmt); + break; case IFN_ASAN_CHECK: if (asan_check_optimize) remove = maybe_optimize_asan_check_ifn (ctx, stmt); @@ -601,7 +693,8 @@ pass_sanopt::execute (function *fun) /* Try to remove redundant checks. */ if (optimize && (flag_sanitize - & (SANITIZE_NULL | SANITIZE_ALIGNMENT | SANITIZE_ADDRESS))) + & (SANITIZE_NULL | SANITIZE_ALIGNMENT + | SANITIZE_ADDRESS | SANITIZE_VPTR))) asan_num_accesses = sanopt_optimize (fun); else if (flag_sanitize & SANITIZE_ADDRESS) { @@ -647,6 +740,9 @@ pass_sanopt::execute (function *fun) case IFN_UBSAN_OBJECT_SIZE: no_next = ubsan_expand_objsize_ifn (&gsi); break; + case IFN_UBSAN_VPTR: + no_next = ubsan_expand_vptr_ifn (&gsi); + break; case IFN_ASAN_CHECK: no_next = asan_expand_check_ifn (&gsi, use_calls); break; diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index b47f7510a07..8a9f9271080 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,15 @@ +2015-01-15 Jakub Jelinek + + * g++.dg/ubsan/vptr-1.C: New test. + * g++.dg/ubsan/vptr-2.C: New test. + * g++.dg/ubsan/vptr-3.C: New test. + * g++.dg/ubsan/vptr-4.C: New test. + * g++.dg/ubsan/vptr-5.C: New test. + * g++.dg/ubsan/vptr-6.C: New test. + * g++.dg/ubsan/vptr-7.C: New test. + * g++.dg/ubsan/vptr-8.C: New test. + * g++.dg/ubsan/vptr-9.C: New test. + 2015-01-15 Eric Botcazou * lib/c-torture.exp: Compute LTO_TORTURE_OPTIONS after the environment diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-1.C b/gcc/testsuite/g++.dg/ubsan/vptr-1.C new file mode 100644 index 00000000000..f4260c1ba41 --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-1.C @@ -0,0 +1,184 @@ +// { dg-do run { target { ilp32 || lp64 } } } +// { dg-options "-fsanitize=vptr" } + +struct S +{ + S() : a(0) {} + ~S() {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S +{ + T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } +}; + +struct U : S, T { virtual int v() { return 2; } }; // { dg-warning "direct base .S. inaccessible in .U. due to ambiguity" } +struct V : S {}; + +void +foo () +{ + T t; + (void)t.a; + (void)t.b; + (void)t.f(); + (void)t.g(); + (void)t.v(); + (void)t.S::v(); + + U u; + (void)u.T::a; + (void)u.b; + (void)u.T::f(); + (void)u.g(); + (void)u.v(); + (void)u.T::v(); + (void)((T&)u).S::v(); +} + +T *x; + +__attribute__((noinline, noclone)) int +bar (T *p, int q) +{ + switch (q) + { + // These shouldn't fail: + case 0x10: + case 0x20: + case 0x30: + case 0x40: + { + T &r = *p; + break; + } + case 0x21: + case 0x31: + return p->b; + case 0x22: + case 0x32: + return p->g (); + case 0x23: + case 0x33: + x = static_cast(reinterpret_cast(p)); + break; + case 0x44: + return reinterpret_cast(p)->v() - 2; + // These should: + case 0x11: + return p->b; + // { dg-output "\[^\n\r]*vptr-1.C:75:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x12: + return p->g (); + // { dg-output "\[^\n\r]*vptr-1.C:82:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x13: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-1.C:89:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x34: + return reinterpret_cast(p)->v() - 2; + // { dg-output "\[^\n\r]*vptr-1.C:97:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'U'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 16 within object of type 'U'(\n|\r\n|\r)" { target lp64 } } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 8 within object of type 'U'(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^ ~~~~~~~~~~~~~~~~~~~~~~~(\n|\r\n|\r)" { target lp64 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^ ~~~~~~~~~~~(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + case 0x41: + return p->b; + // { dg-output "\[^\n\r]*vptr-1.C:107:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x42: + return p->g (); + // { dg-output "\[^\n\r]*vptr-1.C:114:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x43: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-1.C:121:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x51: + return p->b; + // { dg-output "\[^\n\r]*vptr-1.C:129:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object has invalid vptr(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. 00 00 00 00 00 00 00 00 \[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^~~~~~~~~~~~~~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " ?.. .. .. .. ?00 00 00 00 ?.. .. .. .. ?\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " invalid vptr" } + } + return 0; +} + +char b[sizeof (U)] __attribute__((aligned (__alignof__ (U)))) = {}; + +__attribute__((noinline, noclone)) void +baz (int q) +{ + T *p = 0; + S *s = 0; + U *u = 0; + switch (q) + { + case 0x10: case 0x11: case 0x12: case 0x13: + s = new S; + bar (reinterpret_cast(s), q); + delete s; + break; + case 0x20: case 0x21: case 0x22: case 0x23: + p = new T; + bar (p, q); + delete p; + break; + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + u = new U; + bar (u, q); + delete u; + break; + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: + u = new U; + bar (reinterpret_cast(u), q); + delete u; + break; + case 0x51: + p = reinterpret_cast(b); + bar (p, q); + break; + } +} + +int +main () +{ + foo (); + for (int q = 0; q < 0x52; q++) + baz (q); +} diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-2.C b/gcc/testsuite/g++.dg/ubsan/vptr-2.C new file mode 100644 index 00000000000..2aa70469a62 --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-2.C @@ -0,0 +1,184 @@ +// { dg-do run { target { ilp32 || lp64 } } } +// { dg-options "-fsanitize=vptr" } + +struct S +{ + S() : a(0) {} + ~S() {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S +{ + T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } +}; + +struct U : S, T { virtual int v() { return 2; } }; // { dg-warning "direct base .S. inaccessible in .U. due to ambiguity" } +struct V : S {}; + +void +foo () +{ + T t; + (void)t.a; + (void)t.b; + (void)t.f(); + (void)t.g(); + (void)t.v(); + (void)t.S::v(); + + U u; + (void)u.T::a; + (void)u.b; + (void)u.T::f(); + (void)u.g(); + (void)u.v(); + (void)u.T::v(); + (void)((T&)u).S::v(); +} + +T *x; +template +__attribute__((noinline, noclone)) int +bar (T *p, int q) +{ + switch (q) + { + // These shouldn't fail: + case 0x10: + case 0x20: + case 0x30: + case 0x40: + { + T &r = *p; + break; + } + case 0x21: + case 0x31: + return p->b; + case 0x22: + case 0x32: + return p->g (); + case 0x23: + case 0x33: + x = static_cast(reinterpret_cast(p)); + break; + case 0x44: + return reinterpret_cast(p)->v() - 2; + // These should: + case 0x11: + return p->b; + // { dg-output "\[^\n\r]*vptr-2.C:75:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x12: + return p->g (); + // { dg-output "\[^\n\r]*vptr-2.C:82:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x13: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-2.C:89:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x34: + return reinterpret_cast(p)->v() - 2; + // { dg-output "\[^\n\r]*vptr-2.C:97:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'U'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 16 within object of type 'U'(\n|\r\n|\r)" { target lp64 } } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 8 within object of type 'U'(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^ ~~~~~~~~~~~~~~~~~~~~~~~(\n|\r\n|\r)" { target lp64 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^ ~~~~~~~~~~~(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + case 0x41: + return p->b; + // { dg-output "\[^\n\r]*vptr-2.C:107:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x42: + return p->g (); + // { dg-output "\[^\n\r]*vptr-2.C:114:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x43: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-2.C:121:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x51: + return p->b; + // { dg-output "\[^\n\r]*vptr-2.C:129:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object has invalid vptr(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. 00 00 00 00 00 00 00 00 \[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^~~~~~~~~~~~~~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " ?.. .. .. .. ?00 00 00 00 ?.. .. .. .. ?\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " invalid vptr" } + } + return 0; +} + +char b[sizeof (U)] __attribute__((aligned (__alignof__ (U)))) = {}; + +__attribute__((noinline, noclone)) void +baz (int q) +{ + T *p = 0; + S *s = 0; + U *u = 0; + switch (q) + { + case 0x10: case 0x11: case 0x12: case 0x13: + s = new S; + bar (reinterpret_cast(s), q); + delete s; + break; + case 0x20: case 0x21: case 0x22: case 0x23: + p = new T; + bar (p, q); + delete p; + break; + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + u = new U; + bar (u, q); + delete u; + break; + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: + u = new U; + bar (reinterpret_cast(u), q); + delete u; + break; + case 0x51: + p = reinterpret_cast(b); + bar (p, q); + break; + } +} + +int +main () +{ + foo (); + for (int q = 0; q < 0x52; q++) + baz (q); +} diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-3.C b/gcc/testsuite/g++.dg/ubsan/vptr-3.C new file mode 100644 index 00000000000..916d8ef8e28 --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-3.C @@ -0,0 +1,184 @@ +// { dg-do run { target { ilp32 || lp64 } } } +// { dg-options "-fsanitize=vptr" } + +struct S +{ + S() : a(0) {} + ~S() {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S +{ + T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } +}; + +struct U : S, T { virtual int v() { return 2; } }; // { dg-warning "direct base .S. inaccessible in .U. due to ambiguity" } +struct V : S {}; + +void +foo () +{ + T t; + (void)t.a; + (void)t.b; + (void)t.f(); + (void)t.g(); + (void)t.v(); + (void)t.S::v(); + + U u; + (void)u.T::a; + (void)u.b; + (void)u.T::f(); + (void)u.g(); + (void)u.v(); + (void)u.T::v(); + (void)((T&)u).S::v(); +} + +T *x; +template +__attribute__((noinline, noclone)) int +bar (T *p, int q) +{ + switch (q) + { + // These shouldn't fail: + case 0x10: + case 0x20: + case 0x30: + case 0x40: + { + T &r = *p; + break; + } + case 0x21: + case 0x31: + return p->b; + case 0x22: + case 0x32: + return p->g (); + case 0x23: + case 0x33: + x = static_cast(reinterpret_cast(p)); + break; + case 0x44: + return reinterpret_cast(p)->v() - 2; + // These should: + case 0x11: + return p->b; + // { dg-output "\[^\n\r]*vptr-3.C:75:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x12: + return p->g (); + // { dg-output "\[^\n\r]*vptr-3.C:82:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x13: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-3.C:89:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'S'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'S'\[^\n\r]*(\n|\r\n|\r)" } + case 0x34: + return reinterpret_cast(p)->v() - 2; + // { dg-output "\[^\n\r]*vptr-3.C:97:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'U'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 16 within object of type 'U'(\n|\r\n|\r)" { target lp64 } } + // { dg-output "0x\[0-9a-fA-F]*: note: object is base class subobject at offset 8 within object of type 'U'(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^ ~~~~~~~~~~~~~~~~~~~~~~~(\n|\r\n|\r)" { target lp64 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^ ~~~~~~~~~~~(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " vptr for 'T' base class of 'U'\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + case 0x41: + return p->b; + // { dg-output "\[^\n\r]*vptr-3.C:107:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x42: + return p->g (); + // { dg-output "\[^\n\r]*vptr-3.C:114:\[0-9]*: runtime error: member call on address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x43: + x = static_cast(reinterpret_cast(p)); + break; + // { dg-output "\[^\n\r]*vptr-3.C:121:\[0-9]*: runtime error: downcast of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. .. .. .. .. .. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } + // { dg-output " vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } + case 0x51: + return p->b; + // { dg-output "\[^\n\r]*vptr-3.C:129:\[0-9]*: runtime error: member access within address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } + // { dg-output "0x\[0-9a-fA-F]*: note: object has invalid vptr(\n|\r\n|\r)" } + // { dg-output " .. .. .. .. 00 00 00 00 00 00 00 00 \[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " \\^~~~~~~~~~~~~~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target lp64 } } + // { dg-output " ?.. .. .. .. ?00 00 00 00 ?.. .. .. .. ?\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " \\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" { target ilp32 } } + // { dg-output " invalid vptr" } + } + return 0; +} + +char b[sizeof (U)] __attribute__((aligned (__alignof__ (U)))) = {}; + +__attribute__((noinline, noclone)) void +baz (int q) +{ + T *p = 0; + S *s = 0; + U *u = 0; + switch (q) + { + case 0x10: case 0x11: case 0x12: case 0x13: + s = new S; + bar<0> (reinterpret_cast(s), q); + delete s; + break; + case 0x20: case 0x21: case 0x22: case 0x23: + p = new T; + bar<0> (p, q); + delete p; + break; + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + u = new U; + bar<0> (u, q); + delete u; + break; + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: + u = new U; + bar<0> (reinterpret_cast(u), q); + delete u; + break; + case 0x51: + p = reinterpret_cast(b); + bar<0> (p, q); + break; + } +} + +int +main () +{ + foo (); + for (int q = 0; q < 0x52; q++) + baz (q); +} diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-4.C b/gcc/testsuite/g++.dg/ubsan/vptr-4.C new file mode 100644 index 00000000000..1c037d047dd --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-4.C @@ -0,0 +1,54 @@ +// Verify that -fsanitize=vptr downcast instrumentation works properly +// inside of constexpr. +// { dg-do compile } +// { dg-options "-std=c++11 -fsanitize=vptr" } + +struct S { + constexpr S() : a(0) {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S { + constexpr T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } + constexpr const T *foo() { return (const T *) reinterpret_cast (this); } +}; + +constexpr T t; +constexpr const T *p = t.foo (); + +template +struct V { + constexpr V() : a(0) {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +template +struct W : V { + constexpr W() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } + constexpr const W *foo() { return (const W *) reinterpret_cast *> (this); } +}; + +constexpr W w; +constexpr const W *s = w.foo (); + +template +int foo (void) +{ + static constexpr T t; + static constexpr const T *p = t.foo (); + static constexpr W w; + static constexpr const W *s = w.foo (); + return t.b + w.b; +} + +int x = foo (); diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-5.C b/gcc/testsuite/g++.dg/ubsan/vptr-5.C new file mode 100644 index 00000000000..fb9d15cc99e --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-5.C @@ -0,0 +1,32 @@ +// { dg-do run } +// { dg-options "-fsanitize=vptr" } + +struct S +{ + S() : a(0) {} + ~S() {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S +{ + T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } +}; + +T * +foo (S *p) +{ + return (T *) p; +} + +int +main () +{ + if (foo (__null) != __null) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-6.C b/gcc/testsuite/g++.dg/ubsan/vptr-6.C new file mode 100644 index 00000000000..1e595c50c9e --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-6.C @@ -0,0 +1,32 @@ +// { dg-do compile } +// { dg-skip-if "" { *-*-* } { "-flto" } { "" } } +// { dg-options "-fsanitize=vptr -O2 -fdump-tree-optimized" } + +struct S { virtual ~S (); int i; _Complex int j[5]; }; + +int +f1 (S *p) +{ + return p->i; +} + +int +f2 (S *p) +{ + return *&p->i; +} + +_Complex int * +f3 (S *p, S *q) +{ + return &p->j[q->i]; +} + +int +f4 (S &p, S &q) +{ + return __imag__ p.j[q.i]; +} + +// { dg-final { scan-tree-dump-times "__ubsan_handle_dynamic_type_cache_miss" 5 "optimized" } } +// { dg-final { cleanup-tree-dump "optimized" } } diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-7.C b/gcc/testsuite/g++.dg/ubsan/vptr-7.C new file mode 100644 index 00000000000..d3ff1a36d5b --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-7.C @@ -0,0 +1,26 @@ +// { dg-do compile } +// { dg-skip-if "" { *-*-* } { "-flto" } { "" } } +// { dg-options "-fsanitize=vptr -O2 -fdump-tree-optimized" } + +struct S { virtual ~S (); int i; }; + +int * +f1 (S *p) +{ + return &p->i; +} + +int * +f2 (S *p) +{ + return &*&p->i; +} + +int & +f3 (S *p) +{ + return p->i; +} + +// { dg-final { scan-tree-dump-times "__ubsan_handle_dynamic_type_cache_miss" 0 "optimized" } } +// { dg-final { cleanup-tree-dump "optimized" } } diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-8.C b/gcc/testsuite/g++.dg/ubsan/vptr-8.C new file mode 100644 index 00000000000..1533f20f1f1 --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-8.C @@ -0,0 +1,32 @@ +// { dg-do run } +// { dg-shouldfail "ubsan" } +// { dg-options "-fsanitize=vptr -fno-sanitize-recover=vptr" } + +extern "C" void abort (); + +struct S { virtual void f () {} }; +struct T : S { ~T (); }; +struct U : S { }; +struct V : T, virtual U {}; + +U *up; +V *vp; + +int +main () +{ + V v; + up = vp = &v; +} + +T::~T () +{ + if (vp != up) + abort (); +} + +// { dg-output "\[^\n\r]*vptr-8.C:24:\[0-9]*: runtime error: cast to virtual base of address 0x\[0-9a-fA-F]* which does not point to an object of type 'V'(\n|\r\n|\r)" } +// { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'T'(\n|\r\n|\r)" } +// { dg-output " ?.. .. .. .. ?.. .. .. .. ?.. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } +// { dg-output " ?\\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } +// { dg-output " ?vptr for 'T'\[^\n\r]*(\n|\r\n|\r)" } diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-9.C b/gcc/testsuite/g++.dg/ubsan/vptr-9.C new file mode 100644 index 00000000000..482e3e6030d --- /dev/null +++ b/gcc/testsuite/g++.dg/ubsan/vptr-9.C @@ -0,0 +1,22 @@ +// { dg-do run } +// { dg-shouldfail "ubsan" } +// { dg-options "-fsanitize=vptr -fno-sanitize-recover=undefined" } + +struct S { virtual int f () { return 0; } }; +struct T : virtual S {}; +struct U { virtual int f () { return 0; } }; + +int +main () +{ + U u; + T *t = (T *) &u; + S *s = t; + return s->f (); +} + +// { dg-output "\[^\n\r]*vptr-9.C:14:\[0-9]*: runtime error: cast to virtual base of address 0x\[0-9a-fA-F]* which does not point to an object of type 'T'(\n|\r\n|\r)" } +// { dg-output "0x\[0-9a-fA-F]*: note: object is of type 'U'(\n|\r\n|\r)" } +// { dg-output " ?.. .. .. .. ?.. .. .. .. ?.. .. .. .. \[^\n\r]*(\n|\r\n|\r)" } +// { dg-output " ?\\^~~~~~~~~~~\[^\n\r]*(\n|\r\n|\r)" } +// { dg-output " ?vptr for 'U'\[^\n\r]*(\n|\r\n|\r)" } diff --git a/gcc/tree-ssa-alias.c b/gcc/tree-ssa-alias.c index 31db17a7dbf..fa6caef21e1 100644 --- a/gcc/tree-ssa-alias.c +++ b/gcc/tree-ssa-alias.c @@ -1929,6 +1929,22 @@ call_may_clobber_ref_p_1 (gcall *call, ao_ref *ref) if (gimple_call_flags (call) & (ECF_PURE|ECF_CONST|ECF_LOOPING_CONST_OR_PURE|ECF_NOVOPS)) return false; + if (gimple_call_internal_p (call)) + switch (gimple_call_internal_fn (call)) + { + /* Treat these internal calls like ECF_PURE for aliasing, + they don't write to any memory the program should care about. + They have important other side-effects, and read memory, + so can't be ECF_NOVOPS. */ + case IFN_UBSAN_NULL: + case IFN_UBSAN_BOUNDS: + case IFN_UBSAN_VPTR: + case IFN_UBSAN_OBJECT_SIZE: + case IFN_ASAN_CHECK: + return false; + default: + break; + } base = ao_ref_base (ref); if (!base) diff --git a/gcc/ubsan.c b/gcc/ubsan.c index 7c94dab036c..5bf53276d7d 100644 --- a/gcc/ubsan.c +++ b/gcc/ubsan.c @@ -981,6 +981,167 @@ ubsan_expand_objsize_ifn (gimple_stmt_iterator *gsi) return gsi_end_p (*gsi); } +/* Cached __ubsan_vptr_type_cache decl. */ +static GTY(()) tree ubsan_vptr_type_cache_decl; + +/* Expand UBSAN_VPTR internal call. The type is kept on the ckind + argument which is a constant, because the middle-end treats pointer + conversions as useless and therefore the type of the first argument + could be changed to any other pointer type. */ + +bool +ubsan_expand_vptr_ifn (gimple_stmt_iterator *gsip) +{ + gimple_stmt_iterator gsi = *gsip; + gimple stmt = gsi_stmt (gsi); + location_t loc = gimple_location (stmt); + gcc_assert (gimple_call_num_args (stmt) == 5); + tree op = gimple_call_arg (stmt, 0); + tree vptr = gimple_call_arg (stmt, 1); + tree str_hash = gimple_call_arg (stmt, 2); + tree ti_decl_addr = gimple_call_arg (stmt, 3); + tree ckind_tree = gimple_call_arg (stmt, 4); + ubsan_null_ckind ckind = (ubsan_null_ckind) tree_to_uhwi (ckind_tree); + tree type = TREE_TYPE (TREE_TYPE (ckind_tree)); + gimple g; + basic_block fallthru_bb = NULL; + + if (ckind == UBSAN_DOWNCAST_POINTER) + { + /* Guard everything with if (op != NULL) { ... }. */ + basic_block then_bb; + gimple_stmt_iterator cond_insert_point + = create_cond_insert_point (gsip, false, false, true, + &then_bb, &fallthru_bb); + g = gimple_build_cond (NE_EXPR, op, build_zero_cst (TREE_TYPE (op)), + NULL_TREE, NULL_TREE); + gimple_set_location (g, loc); + gsi_insert_after (&cond_insert_point, g, GSI_NEW_STMT); + *gsip = gsi_after_labels (then_bb); + gsi_remove (&gsi, false); + gsi_insert_before (gsip, stmt, GSI_NEW_STMT); + gsi = *gsip; + } + + tree htype = TREE_TYPE (str_hash); + tree cst = wide_int_to_tree (htype, + wi::uhwi (((uint64_t) 0x9ddfea08 << 32) + | 0xeb382d69, 64)); + g = gimple_build_assign (make_ssa_name (htype), BIT_XOR_EXPR, + vptr, str_hash); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), MULT_EXPR, + gimple_assign_lhs (g), cst); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + tree t1 = gimple_assign_lhs (g); + g = gimple_build_assign (make_ssa_name (htype), LSHIFT_EXPR, + t1, build_int_cst (integer_type_node, 47)); + gimple_set_location (g, loc); + tree t2 = gimple_assign_lhs (g); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), BIT_XOR_EXPR, + vptr, t1); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), BIT_XOR_EXPR, + t2, gimple_assign_lhs (g)); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), MULT_EXPR, + gimple_assign_lhs (g), cst); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + tree t3 = gimple_assign_lhs (g); + g = gimple_build_assign (make_ssa_name (htype), LSHIFT_EXPR, + t3, build_int_cst (integer_type_node, 47)); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), BIT_XOR_EXPR, + t3, gimple_assign_lhs (g)); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + g = gimple_build_assign (make_ssa_name (htype), MULT_EXPR, + gimple_assign_lhs (g), cst); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + if (!useless_type_conversion_p (pointer_sized_int_node, htype)) + { + g = gimple_build_assign (make_ssa_name (pointer_sized_int_node), + NOP_EXPR, gimple_assign_lhs (g)); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + } + tree hash = gimple_assign_lhs (g); + + if (ubsan_vptr_type_cache_decl == NULL_TREE) + { + tree atype = build_array_type_nelts (pointer_sized_int_node, 128); + tree array = build_decl (UNKNOWN_LOCATION, VAR_DECL, + get_identifier ("__ubsan_vptr_type_cache"), + atype); + DECL_ARTIFICIAL (array) = 1; + DECL_IGNORED_P (array) = 1; + TREE_PUBLIC (array) = 1; + TREE_STATIC (array) = 1; + DECL_EXTERNAL (array) = 1; + DECL_VISIBILITY (array) = VISIBILITY_DEFAULT; + DECL_VISIBILITY_SPECIFIED (array) = 1; + varpool_node::finalize_decl (array); + ubsan_vptr_type_cache_decl = array; + } + + g = gimple_build_assign (make_ssa_name (pointer_sized_int_node), + BIT_AND_EXPR, hash, + build_int_cst (pointer_sized_int_node, 127)); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + + tree c = build4_loc (loc, ARRAY_REF, pointer_sized_int_node, + ubsan_vptr_type_cache_decl, gimple_assign_lhs (g), + NULL_TREE, NULL_TREE); + g = gimple_build_assign (make_ssa_name (pointer_sized_int_node), + ARRAY_REF, c); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + + basic_block then_bb, fallthru2_bb; + gimple_stmt_iterator cond_insert_point + = create_cond_insert_point (gsip, false, false, true, + &then_bb, &fallthru2_bb); + g = gimple_build_cond (NE_EXPR, gimple_assign_lhs (g), hash, + NULL_TREE, NULL_TREE); + gimple_set_location (g, loc); + gsi_insert_after (&cond_insert_point, g, GSI_NEW_STMT); + *gsip = gsi_after_labels (then_bb); + if (fallthru_bb == NULL) + fallthru_bb = fallthru2_bb; + + tree data + = ubsan_create_data ("__ubsan_vptr_data", 1, &loc, + ubsan_type_descriptor (type), NULL_TREE, ti_decl_addr, + build_int_cst (unsigned_char_type_node, ckind), + NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + enum built_in_function bcode + = (flag_sanitize_recover & SANITIZE_VPTR) + ? BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS + : BUILT_IN_UBSAN_HANDLE_DYNAMIC_TYPE_CACHE_MISS_ABORT; + + g = gimple_build_call (builtin_decl_explicit (bcode), 3, data, op, hash); + gimple_set_location (g, loc); + gsi_insert_before (gsip, g, GSI_SAME_STMT); + + /* Point GSI to next logical statement. */ + *gsip = gsi_start_bb (fallthru_bb); + + /* Get rid of the UBSAN_VPTR call from the IR. */ + unlink_stmt_vdef (stmt); + gsi_remove (&gsi, true); + return gsi_end_p (*gsip); +} + /* Instrument a memory reference. BASE is the base of MEM, IS_LHS says whether the pointer is on the left hand side of the assignment. */ diff --git a/gcc/ubsan.h b/gcc/ubsan.h index 9b8a59b6c84..9f23a382ec0 100644 --- a/gcc/ubsan.h +++ b/gcc/ubsan.h @@ -28,7 +28,11 @@ enum ubsan_null_ckind { UBSAN_REF_BINDING, UBSAN_MEMBER_ACCESS, UBSAN_MEMBER_CALL, - UBSAN_CTOR_CALL + UBSAN_CTOR_CALL, + UBSAN_DOWNCAST_POINTER, + UBSAN_DOWNCAST_REFERENCE, + UBSAN_UPCAST, + UBSAN_CAST_TO_VBASE }; /* This controls how ubsan prints types. Used in ubsan_type_descriptor. */ @@ -42,6 +46,7 @@ extern bool do_ubsan_in_current_function (void); extern bool ubsan_expand_bounds_ifn (gimple_stmt_iterator *); extern bool ubsan_expand_null_ifn (gimple_stmt_iterator *); extern bool ubsan_expand_objsize_ifn (gimple_stmt_iterator *); +extern bool ubsan_expand_vptr_ifn (gimple_stmt_iterator *); extern bool ubsan_instrument_unreachable (gimple_stmt_iterator *); extern tree ubsan_create_data (const char *, int, const location_t *, ...); extern tree ubsan_type_descriptor (tree, enum ubsan_print_style = UBSAN_PRINT_NORMAL);