| /* Optimize and expand sanitizer functions. |
| Copyright (C) 2014-2019 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) |
| { |
| poly_int64 bitsize, pbitpos; |
| 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); |
| |
| HOST_WIDE_INT bitpos; |
| base = get_inner_reference (base, &bitsize, &pbitpos, &offset, &mode, |
| &unsignedp, &reversep, &volatilep); |
| if ((offset == NULL_TREE || TREE_CODE (offset) == INTEGER_CST) |
| && DECL_P (base) |
| && ((!VAR_P (base) |
| && TREE_CODE (base) != PARM_DECL |
| && TREE_CODE (base) != RESULT_DECL) |
| || !DECL_REGISTER (base)) |
| && pbitpos.is_constant (&bitpos)) |
| { |
| offset_int expr_offset; |
| if (offset) |
| expr_offset = wi::to_offset (offset) + bitpos / BITS_PER_UNIT; |
| else |
| expr_offset = bitpos / BITS_PER_UNIT; |
| expr_offset = wi::sext (expr_offset, POINTER_SIZE); |
| 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 (!wi::neg_p (expr_offset) |
| && 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 = !wi::neg_p (expr_offset); |
| |
| 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_THIS_VOLATILE (arg) |
| && 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); |
| |
| /* We skip parameters that have a DECL_VALUE_EXPR. */ |
| if (DECL_HAS_VALUE_EXPR_P (arg)) |
| continue; |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, |
| "Rewriting parameter whose address is taken: "); |
| print_generic_expr (dump_file, arg, dump_flags); |
| fputc ('\n', dump_file); |
| } |
| |
| 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); |
| } |