Mercurial > hg > CbC > CbC_gcc
diff gcc/sanopt.c @ 111:04ced10e8804
gcc 7
author | kono |
---|---|
date | Fri, 27 Oct 2017 22:46:09 +0900 |
parents | |
children | 84e7813d76e9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gcc/sanopt.c Fri Oct 27 22:46:09 2017 +0900 @@ -0,0 +1,1370 @@ +/* Optimize and expand sanitizer functions. + Copyright (C) 2014-2017 Free Software Foundation, Inc. + Contributed by Marek Polacek <polacek@redhat.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "ssa.h" +#include "tree-pass.h" +#include "tree-ssa-operands.h" +#include "gimple-pretty-print.h" +#include "fold-const.h" +#include "gimple-iterator.h" +#include "stringpool.h" +#include "attribs.h" +#include "asan.h" +#include "ubsan.h" +#include "params.h" +#include "tree-hash-traits.h" +#include "gimple-ssa.h" +#include "tree-phinodes.h" +#include "ssa-iterators.h" +#include "gimplify.h" +#include "gimple-iterator.h" +#include "gimple-walk.h" +#include "cfghooks.h" +#include "tree-dfa.h" +#include "tree-ssa.h" +#include "varasm.h" + +/* This is used to carry information about basic blocks. It is + attached to the AUX field of the standard CFG block. */ + +struct sanopt_info +{ + /* True if this BB might call (directly or indirectly) free/munmap + or similar operation. */ + bool has_freeing_call_p; + + /* True if HAS_FREEING_CALL_P flag has been computed. */ + bool has_freeing_call_computed_p; + + /* True if there is a block with HAS_FREEING_CALL_P flag set + on any path between an immediate dominator of BB, denoted + imm(BB), and BB. */ + bool imm_dom_path_with_freeing_call_p; + + /* True if IMM_DOM_PATH_WITH_FREEING_CALL_P has been computed. */ + bool imm_dom_path_with_freeing_call_computed_p; + + /* Number of possibly freeing calls encountered in this bb + (so far). */ + uint64_t freeing_call_events; + + /* True if BB is currently being visited during computation + of IMM_DOM_PATH_WITH_FREEING_CALL_P flag. */ + bool being_visited_p; + + /* True if this BB has been visited in the dominator walk. */ + bool visited_p; +}; + +/* If T has a single definition of form T = T2, return T2. */ + +static tree +maybe_get_single_definition (tree t) +{ + if (TREE_CODE (t) == SSA_NAME) + { + gimple *g = SSA_NAME_DEF_STMT (t); + if (gimple_assign_single_p (g)) + return gimple_assign_rhs1 (g); + } + return NULL_TREE; +} + +/* 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_hash : typed_noop_remove <sanopt_tree_triplet> +{ + typedef sanopt_tree_triplet value_type; + typedef sanopt_tree_triplet compare_type; + + static 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 bool + equal (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); + } + + static void + mark_deleted (sanopt_tree_triplet &ref) + { + ref.t1 = reinterpret_cast<tree> (1); + } + + static void + mark_empty (sanopt_tree_triplet &ref) + { + ref.t1 = NULL; + } + + static bool + is_deleted (const sanopt_tree_triplet &ref) + { + return ref.t1 == reinterpret_cast<tree> (1); + } + + static bool + is_empty (const sanopt_tree_triplet &ref) + { + return ref.t1 == NULL; + } +}; + +/* Tree couple for ptr_check_map. */ +struct sanopt_tree_couple +{ + tree ptr; + bool pos_p; +}; + +/* Traits class for tree triplet hash maps below. */ + +struct sanopt_tree_couple_hash : typed_noop_remove <sanopt_tree_couple> +{ + typedef sanopt_tree_couple value_type; + typedef sanopt_tree_couple compare_type; + + static hashval_t + hash (const sanopt_tree_couple &ref) + { + inchash::hash hstate (0); + inchash::add_expr (ref.ptr, hstate); + hstate.add_int (ref.pos_p); + return hstate.end (); + } + + static bool + equal (const sanopt_tree_couple &ref1, const sanopt_tree_couple &ref2) + { + return operand_equal_p (ref1.ptr, ref2.ptr, 0) + && ref1.pos_p == ref2.pos_p; + } + + static void + mark_deleted (sanopt_tree_couple &ref) + { + ref.ptr = reinterpret_cast<tree> (1); + } + + static void + mark_empty (sanopt_tree_couple &ref) + { + ref.ptr = NULL; + } + + static bool + is_deleted (const sanopt_tree_couple &ref) + { + return ref.ptr == reinterpret_cast<tree> (1); + } + + static bool + is_empty (const sanopt_tree_couple &ref) + { + return ref.ptr == NULL; + } +}; + +/* This is used to carry various hash maps and variables used + in sanopt_optimize_walker. */ + +struct sanopt_ctx +{ + /* This map maps a pointer (the first argument of UBSAN_NULL) to + a vector of UBSAN_NULL call statements that check this pointer. */ + hash_map<tree, auto_vec<gimple *> > null_check_map; + + /* 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_operand_hash, auto_vec<gimple *> > 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_hash, auto_vec<gimple *> > vptr_check_map; + + /* This map maps a couple (tree and boolean) to a vector of UBSAN_PTR + call statements that check that pointer overflow. */ + hash_map<sanopt_tree_couple_hash, auto_vec<gimple *> > ptr_check_map; + + /* Number of IFN_ASAN_CHECK statements. */ + int asan_num_accesses; + + /* True when the current functions constains an ASAN_MARK. */ + bool contains_asan_mark; +}; + +/* Return true if there might be any call to free/munmap operation + on any path in between DOM (which should be imm(BB)) and BB. */ + +static bool +imm_dom_path_with_freeing_call (basic_block bb, basic_block dom) +{ + sanopt_info *info = (sanopt_info *) bb->aux; + edge e; + edge_iterator ei; + + if (info->imm_dom_path_with_freeing_call_computed_p) + return info->imm_dom_path_with_freeing_call_p; + + info->being_visited_p = true; + + FOR_EACH_EDGE (e, ei, bb->preds) + { + sanopt_info *pred_info = (sanopt_info *) e->src->aux; + + if (e->src == dom) + continue; + + if ((pred_info->imm_dom_path_with_freeing_call_computed_p + && pred_info->imm_dom_path_with_freeing_call_p) + || (pred_info->has_freeing_call_computed_p + && pred_info->has_freeing_call_p)) + { + info->imm_dom_path_with_freeing_call_computed_p = true; + info->imm_dom_path_with_freeing_call_p = true; + info->being_visited_p = false; + return true; + } + } + + FOR_EACH_EDGE (e, ei, bb->preds) + { + sanopt_info *pred_info = (sanopt_info *) e->src->aux; + + if (e->src == dom) + continue; + + if (pred_info->has_freeing_call_computed_p) + continue; + + gimple_stmt_iterator gsi; + for (gsi = gsi_start_bb (e->src); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + gasm *asm_stmt; + + if ((is_gimple_call (stmt) && !nonfreeing_call_p (stmt)) + || ((asm_stmt = dyn_cast <gasm *> (stmt)) + && (gimple_asm_clobbers_memory_p (asm_stmt) + || gimple_asm_volatile_p (asm_stmt)))) + { + pred_info->has_freeing_call_p = true; + break; + } + } + + pred_info->has_freeing_call_computed_p = true; + if (pred_info->has_freeing_call_p) + { + info->imm_dom_path_with_freeing_call_computed_p = true; + info->imm_dom_path_with_freeing_call_p = true; + info->being_visited_p = false; + return true; + } + } + + FOR_EACH_EDGE (e, ei, bb->preds) + { + if (e->src == dom) + continue; + + basic_block src; + for (src = e->src; src != dom; ) + { + sanopt_info *pred_info = (sanopt_info *) src->aux; + if (pred_info->being_visited_p) + break; + basic_block imm = get_immediate_dominator (CDI_DOMINATORS, src); + if (imm_dom_path_with_freeing_call (src, imm)) + { + info->imm_dom_path_with_freeing_call_computed_p = true; + info->imm_dom_path_with_freeing_call_p = true; + info->being_visited_p = false; + return true; + } + src = imm; + } + } + + info->imm_dom_path_with_freeing_call_computed_p = true; + info->imm_dom_path_with_freeing_call_p = false; + info->being_visited_p = false; + return false; +} + +/* Get the first dominating check from the list of stored checks. + Non-dominating checks are silently dropped. */ + +static gimple * +maybe_get_dominating_check (auto_vec<gimple *> &v) +{ + for (; !v.is_empty (); v.pop ()) + { + gimple *g = v.last (); + sanopt_info *si = (sanopt_info *) gimple_bb (g)->aux; + if (!si->visited_p) + /* At this point we shouldn't have any statements + that aren't dominating the current BB. */ + return g; + } + return NULL; +} + +/* Optimize away redundant UBSAN_NULL calls. */ + +static bool +maybe_optimize_ubsan_null_ifn (struct sanopt_ctx *ctx, gimple *stmt) +{ + gcc_assert (gimple_call_num_args (stmt) == 3); + tree ptr = gimple_call_arg (stmt, 0); + tree cur_align = gimple_call_arg (stmt, 2); + gcc_assert (TREE_CODE (cur_align) == INTEGER_CST); + bool remove = false; + + auto_vec<gimple *> &v = ctx->null_check_map.get_or_insert (ptr); + gimple *g = maybe_get_dominating_check (v); + if (!g) + { + /* For this PTR we don't have any UBSAN_NULL stmts recorded, so there's + nothing to optimize yet. */ + v.safe_push (stmt); + return false; + } + + /* We already have recorded a UBSAN_NULL check for this pointer. Perhaps we + can drop this one. But only if this check doesn't specify stricter + alignment. */ + + tree align = gimple_call_arg (g, 2); + int kind = tree_to_shwi (gimple_call_arg (g, 1)); + /* If this is a NULL pointer check where we had segv anyway, we can + remove it. */ + if (integer_zerop (align) + && (kind == UBSAN_LOAD_OF + || kind == UBSAN_STORE_OF + || kind == UBSAN_MEMBER_ACCESS)) + remove = true; + /* Otherwise remove the check in non-recovering mode, or if the + stmts have same location. */ + else if (integer_zerop (align)) + remove = (flag_sanitize_recover & SANITIZE_NULL) == 0 + || flag_sanitize_undefined_trap_on_error + || gimple_location (g) == gimple_location (stmt); + else if (tree_int_cst_le (cur_align, align)) + remove = (flag_sanitize_recover & SANITIZE_ALIGNMENT) == 0 + || flag_sanitize_undefined_trap_on_error + || gimple_location (g) == gimple_location (stmt); + + if (!remove && gimple_bb (g) == gimple_bb (stmt) + && tree_int_cst_compare (cur_align, align) == 0) + v.pop (); + + if (!remove) + v.safe_push (stmt); + return remove; +} + +/* Return true when pointer PTR for a given CUR_OFFSET is already sanitized + in a given sanitization context CTX. */ + +static bool +has_dominating_ubsan_ptr_check (sanopt_ctx *ctx, tree ptr, + offset_int &cur_offset) +{ + bool pos_p = !wi::neg_p (cur_offset); + sanopt_tree_couple couple; + couple.ptr = ptr; + couple.pos_p = pos_p; + + auto_vec<gimple *> &v = ctx->ptr_check_map.get_or_insert (couple); + gimple *g = maybe_get_dominating_check (v); + if (!g) + return false; + + /* We already have recorded a UBSAN_PTR check for this pointer. Perhaps we + can drop this one. But only if this check doesn't specify larger offset. + */ + tree offset = gimple_call_arg (g, 1); + gcc_assert (TREE_CODE (offset) == INTEGER_CST); + offset_int ooffset = wi::sext (wi::to_offset (offset), POINTER_SIZE); + + if (pos_p) + { + if (wi::les_p (cur_offset, ooffset)) + return true; + } + else if (!pos_p && wi::les_p (ooffset, cur_offset)) + return true; + + return false; +} + +/* Record UBSAN_PTR check of given context CTX. Register pointer PTR on + a given OFFSET that it's handled by GIMPLE STMT. */ + +static void +record_ubsan_ptr_check_stmt (sanopt_ctx *ctx, gimple *stmt, tree ptr, + const offset_int &offset) +{ + sanopt_tree_couple couple; + couple.ptr = ptr; + couple.pos_p = !wi::neg_p (offset); + + auto_vec<gimple *> &v = ctx->ptr_check_map.get_or_insert (couple); + v.safe_push (stmt); +} + +/* Optimize away redundant UBSAN_PTR calls. */ + +static bool +maybe_optimize_ubsan_ptr_ifn (sanopt_ctx *ctx, gimple *stmt) +{ + HOST_WIDE_INT bitsize, bitpos; + machine_mode mode; + int volatilep = 0, reversep, unsignedp = 0; + tree offset; + + gcc_assert (gimple_call_num_args (stmt) == 2); + tree ptr = gimple_call_arg (stmt, 0); + tree off = gimple_call_arg (stmt, 1); + + if (TREE_CODE (off) != INTEGER_CST) + return false; + + if (integer_zerop (off)) + return true; + + offset_int cur_offset = wi::sext (wi::to_offset (off), POINTER_SIZE); + if (has_dominating_ubsan_ptr_check (ctx, ptr, cur_offset)) + return true; + + tree base = ptr; + if (TREE_CODE (base) == ADDR_EXPR) + { + base = TREE_OPERAND (base, 0); + + base = get_inner_reference (base, &bitsize, &bitpos, &offset, &mode, + &unsignedp, &reversep, &volatilep); + if (offset == NULL_TREE && DECL_P (base)) + { + gcc_assert (!DECL_REGISTER (base)); + offset_int expr_offset = bitpos / BITS_PER_UNIT; + offset_int total_offset = expr_offset + cur_offset; + if (total_offset != wi::sext (total_offset, POINTER_SIZE)) + { + record_ubsan_ptr_check_stmt (ctx, stmt, ptr, cur_offset); + return false; + } + + /* If BASE is a fixed size automatic variable or + global variable defined in the current TU, we don't have + to instrument anything if offset is within address + of the variable. */ + if ((VAR_P (base) + || TREE_CODE (base) == PARM_DECL + || TREE_CODE (base) == RESULT_DECL) + && DECL_SIZE_UNIT (base) + && TREE_CODE (DECL_SIZE_UNIT (base)) == INTEGER_CST + && (!is_global_var (base) || decl_binds_to_current_def_p (base))) + { + offset_int base_size = wi::to_offset (DECL_SIZE_UNIT (base)); + if (bitpos >= 0 + && wi::les_p (total_offset, base_size)) + { + if (!wi::neg_p (total_offset) + && wi::les_p (total_offset, base_size)) + return true; + } + } + + /* Following expression: UBSAN_PTR (&MEM_REF[ptr + x], y) can be + handled as follows: + + 1) sign (x) == sign (y), then check for dominating check of (x + y) + 2) sign (x) != sign (y), then first check if we have a dominating + check for ptr + x. If so, then we have 2 situations: + a) sign (x) == sign (x + y), here we are done, example: + UBSAN_PTR (&MEM_REF[ptr + 100], -50) + b) check for dominating check of ptr + x + y. + */ + + bool sign_cur_offset = !wi::neg_p (cur_offset); + bool sign_expr_offset = bitpos >= 0; + + tree base_addr + = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (base)), base); + + bool add = false; + if (sign_cur_offset == sign_expr_offset) + { + if (has_dominating_ubsan_ptr_check (ctx, base_addr, total_offset)) + return true; + else + add = true; + } + else + { + if (!has_dominating_ubsan_ptr_check (ctx, base_addr, expr_offset)) + ; /* Don't record base_addr + expr_offset, it's not a guarding + check. */ + else + { + bool sign_total_offset = !wi::neg_p (total_offset); + if (sign_expr_offset == sign_total_offset) + return true; + else + { + if (has_dominating_ubsan_ptr_check (ctx, base_addr, + total_offset)) + return true; + else + add = true; + } + } + } + + /* Record a new dominating check for base_addr + total_offset. */ + if (add && !operand_equal_p (base, base_addr, 0)) + record_ubsan_ptr_check_stmt (ctx, stmt, base_addr, + total_offset); + } + } + + /* For this PTR we don't have any UBSAN_PTR stmts recorded, so there's + nothing to optimize yet. */ + record_ubsan_ptr_check_stmt (ctx, stmt, ptr, cur_offset); + + return false; +} + +/* 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<gimple *> &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. */ + +static bool +can_remove_asan_check (auto_vec<gimple *> &v, tree len, basic_block bb) +{ + unsigned int i; + gimple *g; + gimple *to_pop = NULL; + bool remove = false; + basic_block last_bb = bb; + bool cleanup = false; + + FOR_EACH_VEC_ELT_REVERSE (v, i, g) + { + basic_block gbb = gimple_bb (g); + sanopt_info *si = (sanopt_info *) gbb->aux; + if (gimple_uid (g) < si->freeing_call_events) + { + /* If there is a potentially freeing call after g in gbb, we should + remove it from the vector, can't use in optimization. */ + cleanup = true; + continue; + } + + tree glen = gimple_call_arg (g, 2); + gcc_assert (TREE_CODE (glen) == INTEGER_CST); + + /* If we've checked only smaller length than we want to check now, + we can't remove the current stmt. If g is in the same basic block, + we want to remove it though, as the current stmt is better. */ + if (tree_int_cst_lt (glen, len)) + { + if (gbb == bb) + { + to_pop = g; + cleanup = true; + } + continue; + } + + while (last_bb != gbb) + { + /* Paths from last_bb to bb have been checked before. + gbb is necessarily a dominator of last_bb, but not necessarily + immediate dominator. */ + if (((sanopt_info *) last_bb->aux)->freeing_call_events) + break; + + basic_block imm = get_immediate_dominator (CDI_DOMINATORS, last_bb); + gcc_assert (imm); + if (imm_dom_path_with_freeing_call (last_bb, imm)) + break; + + last_bb = imm; + } + if (last_bb == gbb) + remove = true; + break; + } + + if (cleanup) + { + unsigned int j = 0, l = v.length (); + for (i = 0; i < l; i++) + if (v[i] != to_pop + && (gimple_uid (v[i]) + == ((sanopt_info *) + gimple_bb (v[i])->aux)->freeing_call_events)) + { + if (i != j) + v[j] = v[i]; + j++; + } + v.truncate (j); + } + + return remove; +} + +/* Optimize away redundant ASAN_CHECK calls. */ + +static bool +maybe_optimize_asan_check_ifn (struct sanopt_ctx *ctx, gimple *stmt) +{ + gcc_assert (gimple_call_num_args (stmt) == 4); + tree ptr = gimple_call_arg (stmt, 1); + tree len = gimple_call_arg (stmt, 2); + basic_block bb = gimple_bb (stmt); + sanopt_info *info = (sanopt_info *) bb->aux; + + if (TREE_CODE (len) != INTEGER_CST) + return false; + if (integer_zerop (len)) + return false; + + gimple_set_uid (stmt, info->freeing_call_events); + + auto_vec<gimple *> *ptr_checks = &ctx->asan_check_map.get_or_insert (ptr); + + tree base_addr = maybe_get_single_definition (ptr); + auto_vec<gimple *> *base_checks = NULL; + if (base_addr) + { + base_checks = &ctx->asan_check_map.get_or_insert (base_addr); + /* Original pointer might have been invalidated. */ + ptr_checks = ctx->asan_check_map.get (ptr); + } + + gimple *g = maybe_get_dominating_check (*ptr_checks); + gimple *g2 = NULL; + + if (base_checks) + /* Try with base address as well. */ + g2 = maybe_get_dominating_check (*base_checks); + + if (g == NULL && g2 == NULL) + { + /* For this PTR we don't have any ASAN_CHECK stmts recorded, so there's + nothing to optimize yet. */ + ptr_checks->safe_push (stmt); + if (base_checks) + base_checks->safe_push (stmt); + return false; + } + + bool remove = false; + + if (ptr_checks) + remove = can_remove_asan_check (*ptr_checks, len, bb); + + if (!remove && base_checks) + /* Try with base address as well. */ + remove = can_remove_asan_check (*base_checks, len, bb); + + if (!remove) + { + ptr_checks->safe_push (stmt); + if (base_checks) + base_checks->safe_push (stmt); + } + + return remove; +} + +/* Try to optimize away redundant UBSAN_NULL and ASAN_CHECK calls. + + We walk blocks in the CFG via a depth first search of the dominator + tree; we push unique UBSAN_NULL or ASAN_CHECK statements into a vector + in the NULL_CHECK_MAP or ASAN_CHECK_MAP hash maps as we enter the + blocks. When leaving a block, we mark the block as visited; then + when checking the statements in the vector, we ignore statements that + are coming from already visited blocks, because these cannot dominate + anything anymore. CTX is a sanopt context. */ + +static void +sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx) +{ + basic_block son; + gimple_stmt_iterator gsi; + sanopt_info *info = (sanopt_info *) bb->aux; + bool asan_check_optimize = (flag_sanitize & SANITIZE_ADDRESS) != 0; + + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + bool remove = false; + + if (!is_gimple_call (stmt)) + { + /* Handle asm volatile or asm with "memory" clobber + the same as potentionally freeing call. */ + gasm *asm_stmt = dyn_cast <gasm *> (stmt); + if (asm_stmt + && asan_check_optimize + && (gimple_asm_clobbers_memory_p (asm_stmt) + || gimple_asm_volatile_p (asm_stmt))) + info->freeing_call_events++; + gsi_next (&gsi); + continue; + } + + if (asan_check_optimize && !nonfreeing_call_p (stmt)) + info->freeing_call_events++; + + /* If __asan_before_dynamic_init ("module"); is followed by + __asan_after_dynamic_init (); without intervening memory loads/stores, + there is nothing to guard, so optimize both away. */ + if (asan_check_optimize + && gimple_call_builtin_p (stmt, BUILT_IN_ASAN_BEFORE_DYNAMIC_INIT)) + { + use_operand_p use; + gimple *use_stmt; + if (single_imm_use (gimple_vdef (stmt), &use, &use_stmt)) + { + if (is_gimple_call (use_stmt) + && gimple_call_builtin_p (use_stmt, + BUILT_IN_ASAN_AFTER_DYNAMIC_INIT)) + { + unlink_stmt_vdef (use_stmt); + gimple_stmt_iterator gsi2 = gsi_for_stmt (use_stmt); + gsi_remove (&gsi2, true); + remove = true; + } + } + } + + if (gimple_call_internal_p (stmt)) + switch (gimple_call_internal_fn (stmt)) + { + 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_UBSAN_PTR: + remove = maybe_optimize_ubsan_ptr_ifn (ctx, stmt); + break; + case IFN_ASAN_CHECK: + if (asan_check_optimize) + remove = maybe_optimize_asan_check_ifn (ctx, stmt); + if (!remove) + ctx->asan_num_accesses++; + break; + case IFN_ASAN_MARK: + ctx->contains_asan_mark = true; + break; + default: + break; + } + + if (remove) + { + /* Drop this check. */ + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "Optimizing out: "); + print_gimple_stmt (dump_file, stmt, 0, dump_flags); + } + unlink_stmt_vdef (stmt); + gsi_remove (&gsi, true); + } + else + { + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "Leaving: "); + print_gimple_stmt (dump_file, stmt, 0, dump_flags); + } + + gsi_next (&gsi); + } + } + + if (asan_check_optimize) + { + info->has_freeing_call_p = info->freeing_call_events != 0; + info->has_freeing_call_computed_p = true; + } + + for (son = first_dom_son (CDI_DOMINATORS, bb); + son; + son = next_dom_son (CDI_DOMINATORS, son)) + sanopt_optimize_walker (son, ctx); + + /* We're leaving this BB, so mark it to that effect. */ + info->visited_p = true; +} + +/* Try to remove redundant sanitizer checks in function FUN. */ + +static int +sanopt_optimize (function *fun, bool *contains_asan_mark) +{ + struct sanopt_ctx ctx; + ctx.asan_num_accesses = 0; + ctx.contains_asan_mark = false; + + /* Set up block info for each basic block. */ + alloc_aux_for_blocks (sizeof (sanopt_info)); + + /* We're going to do a dominator walk, so ensure that we have + dominance information. */ + calculate_dominance_info (CDI_DOMINATORS); + + /* Recursively walk the dominator tree optimizing away + redundant checks. */ + sanopt_optimize_walker (ENTRY_BLOCK_PTR_FOR_FN (fun), &ctx); + + free_aux_for_blocks (); + + *contains_asan_mark = ctx.contains_asan_mark; + return ctx.asan_num_accesses; +} + +/* Perform optimization of sanitize functions. */ + +namespace { + +const pass_data pass_data_sanopt = +{ + GIMPLE_PASS, /* type */ + "sanopt", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + ( PROP_ssa | PROP_cfg | PROP_gimple_leh ), /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_update_ssa, /* todo_flags_finish */ +}; + +class pass_sanopt : public gimple_opt_pass +{ +public: + pass_sanopt (gcc::context *ctxt) + : gimple_opt_pass (pass_data_sanopt, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *) { return flag_sanitize; } + virtual unsigned int execute (function *); + +}; // class pass_sanopt + +/* Sanitize all ASAN_MARK unpoison calls that are not reachable by a BB + that contains an ASAN_MARK poison. All these ASAN_MARK unpoison call + can be removed as all variables are unpoisoned in a function prologue. */ + +static void +sanitize_asan_mark_unpoison (void) +{ + /* 1) Find all BBs that contain an ASAN_MARK poison call. */ + auto_sbitmap with_poison (last_basic_block_for_fn (cfun) + 1); + bitmap_clear (with_poison); + basic_block bb; + + FOR_EACH_BB_FN (bb, cfun) + { + if (bitmap_bit_p (with_poison, bb->index)) + continue; + + gimple_stmt_iterator gsi; + for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (asan_mark_p (stmt, ASAN_MARK_POISON)) + { + bitmap_set_bit (with_poison, bb->index); + break; + } + } + } + + auto_sbitmap poisoned (last_basic_block_for_fn (cfun) + 1); + bitmap_clear (poisoned); + auto_sbitmap worklist (last_basic_block_for_fn (cfun) + 1); + bitmap_copy (worklist, with_poison); + + /* 2) Propagate the information to all reachable blocks. */ + while (!bitmap_empty_p (worklist)) + { + unsigned i = bitmap_first_set_bit (worklist); + bitmap_clear_bit (worklist, i); + basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i); + gcc_assert (bb); + + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bb->succs) + if (!bitmap_bit_p (poisoned, e->dest->index)) + { + bitmap_set_bit (poisoned, e->dest->index); + bitmap_set_bit (worklist, e->dest->index); + } + } + + /* 3) Iterate all BBs not included in POISONED BBs and remove unpoison + ASAN_MARK preceding an ASAN_MARK poison (which can still happen). */ + FOR_EACH_BB_FN (bb, cfun) + { + if (bitmap_bit_p (poisoned, bb->index)) + continue; + + gimple_stmt_iterator gsi; + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + if (gimple_call_internal_p (stmt, IFN_ASAN_MARK)) + { + if (asan_mark_p (stmt, ASAN_MARK_POISON)) + break; + else + { + if (dump_file) + fprintf (dump_file, "Removing ASAN_MARK unpoison\n"); + unlink_stmt_vdef (stmt); + release_defs (stmt); + gsi_remove (&gsi, true); + continue; + } + } + + gsi_next (&gsi); + } + } +} + +/* Return true when STMT is either ASAN_CHECK call or a call of a function + that can contain an ASAN_CHECK. */ + +static bool +maybe_contains_asan_check (gimple *stmt) +{ + if (is_gimple_call (stmt)) + { + if (gimple_call_internal_p (stmt, IFN_ASAN_MARK)) + return false; + else + return !(gimple_call_flags (stmt) & ECF_CONST); + } + else if (is_a<gasm *> (stmt)) + return true; + + return false; +} + +/* Sanitize all ASAN_MARK poison calls that are not followed by an ASAN_CHECK + call. These calls can be removed. */ + +static void +sanitize_asan_mark_poison (void) +{ + /* 1) Find all BBs that possibly contain an ASAN_CHECK. */ + auto_sbitmap with_check (last_basic_block_for_fn (cfun) + 1); + bitmap_clear (with_check); + basic_block bb; + + FOR_EACH_BB_FN (bb, cfun) + { + gimple_stmt_iterator gsi; + for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (maybe_contains_asan_check (stmt)) + { + bitmap_set_bit (with_check, bb->index); + break; + } + } + } + + auto_sbitmap can_reach_check (last_basic_block_for_fn (cfun) + 1); + bitmap_clear (can_reach_check); + auto_sbitmap worklist (last_basic_block_for_fn (cfun) + 1); + bitmap_copy (worklist, with_check); + + /* 2) Propagate the information to all definitions blocks. */ + while (!bitmap_empty_p (worklist)) + { + unsigned i = bitmap_first_set_bit (worklist); + bitmap_clear_bit (worklist, i); + basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i); + gcc_assert (bb); + + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bb->preds) + if (!bitmap_bit_p (can_reach_check, e->src->index)) + { + bitmap_set_bit (can_reach_check, e->src->index); + bitmap_set_bit (worklist, e->src->index); + } + } + + /* 3) Iterate all BBs not included in CAN_REACH_CHECK BBs and remove poison + ASAN_MARK not followed by a call to function having an ASAN_CHECK. */ + FOR_EACH_BB_FN (bb, cfun) + { + if (bitmap_bit_p (can_reach_check, bb->index)) + continue; + + gimple_stmt_iterator gsi; + for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + if (maybe_contains_asan_check (stmt)) + break; + else if (asan_mark_p (stmt, ASAN_MARK_POISON)) + { + if (dump_file) + fprintf (dump_file, "Removing ASAN_MARK poison\n"); + unlink_stmt_vdef (stmt); + release_defs (stmt); + gimple_stmt_iterator gsi2 = gsi; + gsi_prev (&gsi); + gsi_remove (&gsi2, true); + continue; + } + + gsi_prev (&gsi); + } + } +} + +/* Rewrite all usages of tree OP which is a PARM_DECL with a VAR_DECL + that is it's DECL_VALUE_EXPR. */ + +static tree +rewrite_usage_of_param (tree *op, int *walk_subtrees, void *) +{ + if (TREE_CODE (*op) == PARM_DECL && DECL_HAS_VALUE_EXPR_P (*op)) + { + *op = DECL_VALUE_EXPR (*op); + *walk_subtrees = 0; + } + + return NULL; +} + +/* For a given function FUN, rewrite all addressable parameters so that + a new automatic variable is introduced. Right after function entry + a parameter is assigned to the variable. */ + +static void +sanitize_rewrite_addressable_params (function *fun) +{ + gimple *g; + gimple_seq stmts = NULL; + bool has_any_addressable_param = false; + auto_vec<tree> clear_value_expr_list; + + for (tree arg = DECL_ARGUMENTS (current_function_decl); + arg; arg = DECL_CHAIN (arg)) + { + tree type = TREE_TYPE (arg); + if (TREE_ADDRESSABLE (arg) && !TREE_ADDRESSABLE (type) + && TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST) + { + TREE_ADDRESSABLE (arg) = 0; + /* The parameter is no longer addressable. */ + has_any_addressable_param = true; + + /* Create a new automatic variable. */ + tree var = build_decl (DECL_SOURCE_LOCATION (arg), + VAR_DECL, DECL_NAME (arg), type); + TREE_ADDRESSABLE (var) = 1; + DECL_IGNORED_P (var) = 1; + + gimple_add_tmp_var (var); + + if (dump_file) + fprintf (dump_file, + "Rewriting parameter whose address is taken: %s\n", + IDENTIFIER_POINTER (DECL_NAME (arg))); + + gcc_assert (!DECL_HAS_VALUE_EXPR_P (arg)); + + SET_DECL_PT_UID (var, DECL_PT_UID (arg)); + + /* Assign value of parameter to newly created variable. */ + if ((TREE_CODE (type) == COMPLEX_TYPE + || TREE_CODE (type) == VECTOR_TYPE)) + { + /* We need to create a SSA name that will be used for the + assignment. */ + DECL_GIMPLE_REG_P (arg) = 1; + tree tmp = get_or_create_ssa_default_def (cfun, arg); + g = gimple_build_assign (var, tmp); + gimple_set_location (g, DECL_SOURCE_LOCATION (arg)); + gimple_seq_add_stmt (&stmts, g); + } + else + { + g = gimple_build_assign (var, arg); + gimple_set_location (g, DECL_SOURCE_LOCATION (arg)); + gimple_seq_add_stmt (&stmts, g); + } + + if (target_for_debug_bind (arg)) + { + g = gimple_build_debug_bind (arg, var, NULL); + gimple_seq_add_stmt (&stmts, g); + clear_value_expr_list.safe_push (arg); + } + + DECL_HAS_VALUE_EXPR_P (arg) = 1; + SET_DECL_VALUE_EXPR (arg, var); + } + } + + if (!has_any_addressable_param) + return; + + /* Replace all usages of PARM_DECLs with the newly + created variable VAR. */ + basic_block bb; + FOR_EACH_BB_FN (bb, fun) + { + gimple_stmt_iterator gsi; + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + gimple_stmt_iterator it = gsi_for_stmt (stmt); + walk_gimple_stmt (&it, NULL, rewrite_usage_of_param, NULL); + } + for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gphi *phi = dyn_cast<gphi *> (gsi_stmt (gsi)); + for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i) + { + hash_set<tree> visited_nodes; + walk_tree (gimple_phi_arg_def_ptr (phi, i), + rewrite_usage_of_param, NULL, &visited_nodes); + } + } + } + + /* Unset value expr for parameters for which we created debug bind + expressions. */ + unsigned i; + tree arg; + FOR_EACH_VEC_ELT (clear_value_expr_list, i, arg) + { + DECL_HAS_VALUE_EXPR_P (arg) = 0; + SET_DECL_VALUE_EXPR (arg, NULL_TREE); + } + + /* Insert default assignments at the beginning of a function. */ + basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (fun); + entry_bb = split_edge (single_succ_edge (entry_bb)); + + gimple_stmt_iterator gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_before (&gsi, stmts, GSI_NEW_STMT); +} + +unsigned int +pass_sanopt::execute (function *fun) +{ + basic_block bb; + int asan_num_accesses = 0; + bool contains_asan_mark = false; + + /* Try to remove redundant checks. */ + if (optimize + && (flag_sanitize + & (SANITIZE_NULL | SANITIZE_ALIGNMENT + | SANITIZE_ADDRESS | SANITIZE_VPTR | SANITIZE_POINTER_OVERFLOW))) + asan_num_accesses = sanopt_optimize (fun, &contains_asan_mark); + else if (flag_sanitize & SANITIZE_ADDRESS) + { + gimple_stmt_iterator gsi; + FOR_EACH_BB_FN (bb, fun) + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (gimple_call_internal_p (stmt, IFN_ASAN_CHECK)) + ++asan_num_accesses; + else if (gimple_call_internal_p (stmt, IFN_ASAN_MARK)) + contains_asan_mark = true; + } + } + + if (contains_asan_mark) + { + sanitize_asan_mark_unpoison (); + sanitize_asan_mark_poison (); + } + + if (asan_sanitize_stack_p ()) + sanitize_rewrite_addressable_params (fun); + + bool use_calls = ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD < INT_MAX + && asan_num_accesses >= ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD; + + hash_map<tree, tree> shadow_vars_mapping; + bool need_commit_edge_insert = false; + FOR_EACH_BB_FN (bb, fun) + { + gimple_stmt_iterator gsi; + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); ) + { + gimple *stmt = gsi_stmt (gsi); + bool no_next = false; + + if (!is_gimple_call (stmt)) + { + gsi_next (&gsi); + continue; + } + + if (gimple_call_internal_p (stmt)) + { + enum internal_fn ifn = gimple_call_internal_fn (stmt); + switch (ifn) + { + case IFN_UBSAN_NULL: + no_next = ubsan_expand_null_ifn (&gsi); + break; + case IFN_UBSAN_BOUNDS: + no_next = ubsan_expand_bounds_ifn (&gsi); + break; + case IFN_UBSAN_OBJECT_SIZE: + no_next = ubsan_expand_objsize_ifn (&gsi); + break; + case IFN_UBSAN_PTR: + no_next = ubsan_expand_ptr_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; + case IFN_ASAN_MARK: + no_next = asan_expand_mark_ifn (&gsi); + break; + case IFN_ASAN_POISON: + no_next = asan_expand_poison_ifn (&gsi, + &need_commit_edge_insert, + shadow_vars_mapping); + break; + default: + break; + } + } + else if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + tree callee = gimple_call_fndecl (stmt); + switch (DECL_FUNCTION_CODE (callee)) + { + case BUILT_IN_UNREACHABLE: + if (sanitize_flags_p (SANITIZE_UNREACHABLE)) + no_next = ubsan_instrument_unreachable (&gsi); + break; + default: + break; + } + } + + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "Expanded: "); + print_gimple_stmt (dump_file, stmt, 0, dump_flags); + } + + if (!no_next) + gsi_next (&gsi); + } + } + + if (need_commit_edge_insert) + gsi_commit_edge_inserts (); + + return 0; +} + +} // anon namespace + +gimple_opt_pass * +make_pass_sanopt (gcc::context *ctxt) +{ + return new pass_sanopt (ctxt); +}