RAP-optimizations/src/size_overflow_plugin/size_overflow_transform_core.c
2018-08-20 04:10:13 +08:00

1014 lines
31 KiB
C

/*
* Copyright 2011-2017 by Emese Revfy <re.emese@gmail.com>
* Licensed under the GPL v2
*
* Homepage:
* https://github.com/ephox-gcc-plugins/size_overflow
*
* Documentation:
* http://forums.grsecurity.net/viewtopic.php?f=7&t=3043
*
* This plugin recomputes expressions of function arguments marked by a size_overflow attribute
* with double integer precision (DImode/TImode for 32/64 bit integer types).
* The recomputed argument is checked against TYPE_MAX and an event is logged on overflow and the triggering process is killed.
*
* Usage:
* $ make
* $ make run
*/
#include "size_overflow.h"
#define MIN_CHECK true
#define MAX_CHECK false
unsigned int call_count = 0;
tree get_size_overflow_type(struct visited *visited, const_gimple stmt, const_tree node)
{
const_tree type;
tree new_type;
gcc_assert(node != NULL_TREE);
type = TREE_TYPE(node);
if (pointer_set_contains(visited->my_stmts, stmt))
return TREE_TYPE(node);
switch (TYPE_MODE(type)) {
case QImode:
case HImode:
new_type = size_overflow_type_SI;
break;
case SImode:
new_type = size_overflow_type_DI;
break;
case DImode:
if (LONG_TYPE_SIZE == GET_MODE_BITSIZE(SImode))
new_type = TYPE_UNSIGNED(type) ? unsigned_intDI_type_node : intDI_type_node;
else
new_type = size_overflow_type_TI;
break;
case TImode:
gcc_assert(!TYPE_UNSIGNED(type));
new_type = size_overflow_type_TI;
break;
default:
debug_tree(node);
error("%s: unsupported gcc configuration (%qE).", __func__, current_function_decl);
gcc_unreachable();
}
if (TYPE_QUALS(type) != 0)
return build_qualified_type(new_type, TYPE_QUALS(type));
return new_type;
}
tree cast_to_new_size_overflow_type(struct visited *visited, gimple stmt, tree rhs, tree size_overflow_type, bool before)
{
gimple_stmt_iterator gsi;
gimple new_stmt;
if (rhs == NULL_TREE)
return NULL_TREE;
gsi = gsi_for_stmt(stmt);
new_stmt = build_cast_stmt(visited, size_overflow_type, rhs, CREATE_NEW_VAR, &gsi, before, false);
if (gimple_assign_cast_p(new_stmt))
gimple_assign_set_rhs_code(new_stmt, CONVERT_EXPR);
pointer_set_insert(visited->my_stmts, new_stmt);
return get_lhs(new_stmt);
}
tree create_assign(struct visited *visited, gimple oldstmt, tree rhs1, bool before)
{
tree lhs, dst_type;
gimple_stmt_iterator gsi;
if (rhs1 == NULL_TREE) {
debug_gimple_stmt(oldstmt);
error("%s: rhs1 is NULL_TREE", __func__);
gcc_unreachable();
}
switch (gimple_code(oldstmt)) {
case GIMPLE_ASM:
lhs = rhs1;
break;
case GIMPLE_CALL:
case GIMPLE_ASSIGN:
lhs = gimple_get_lhs(oldstmt);
break;
default:
debug_gimple_stmt(oldstmt);
gcc_unreachable();
}
gsi = gsi_for_stmt(oldstmt);
pointer_set_insert(visited->stmts, oldstmt);
if (lookup_stmt_eh_lp(oldstmt) != 0) {
basic_block next_bb, cur_bb;
const_edge e;
gcc_assert(before == false);
gcc_assert(stmt_can_throw_internal(oldstmt));
gcc_assert(gimple_code(oldstmt) == GIMPLE_CALL);
gcc_assert(!gsi_end_p(gsi));
cur_bb = gimple_bb(oldstmt);
next_bb = cur_bb->next_bb;
e = find_edge(cur_bb, next_bb);
gcc_assert(e != NULL);
gcc_assert(e->flags & EDGE_FALLTHRU);
gsi = gsi_after_labels(next_bb);
gcc_assert(!gsi_end_p(gsi));
before = true;
oldstmt = gsi_stmt(gsi);
}
if (is_gimple_constant(rhs1) && TREE_CODE_CLASS(gimple_assign_rhs_code(oldstmt)) == tcc_comparison)
dst_type = get_size_overflow_type(visited, oldstmt, rhs1);
else
dst_type = get_size_overflow_type(visited, oldstmt, lhs);
if (is_gimple_constant(rhs1))
return cast_a_tree(dst_type, rhs1);
return cast_to_new_size_overflow_type(visited, oldstmt, rhs1, dst_type, before);
}
tree dup_assign(struct visited *visited, gassign *oldstmt, const_tree node, tree rhs1, tree rhs2, tree __unused rhs3)
{
gassign *stmt;
gimple_stmt_iterator gsi;
tree size_overflow_type, new_var, lhs = gimple_assign_lhs(oldstmt);
if (pointer_set_contains(visited->my_stmts, oldstmt))
return lhs;
if (gimple_num_ops(oldstmt) != 4 && rhs1 == NULL_TREE) {
rhs1 = gimple_assign_rhs1(oldstmt);
rhs1 = create_assign(visited, oldstmt, rhs1, BEFORE_STMT);
}
if (gimple_num_ops(oldstmt) == 3 && rhs2 == NULL_TREE) {
rhs2 = gimple_assign_rhs2(oldstmt);
rhs2 = create_assign(visited, oldstmt, rhs2, BEFORE_STMT);
}
stmt = as_a_gassign(gimple_copy(oldstmt));
gimple_set_location(stmt, gimple_location(oldstmt));
pointer_set_insert(visited->my_stmts, stmt);
if (gimple_assign_rhs_code(oldstmt) == WIDEN_MULT_EXPR)
gimple_assign_set_rhs_code(stmt, MULT_EXPR);
size_overflow_type = get_size_overflow_type(visited, oldstmt, node);
new_var = create_new_var(size_overflow_type);
new_var = make_ssa_name(new_var, stmt);
gimple_assign_set_lhs(stmt, new_var);
if (rhs1 != NULL_TREE)
gimple_assign_set_rhs1(stmt, rhs1);
if (rhs2 != NULL_TREE)
gimple_assign_set_rhs2(stmt, rhs2);
#if BUILDING_GCC_VERSION >= 4006
if (rhs3 != NULL_TREE)
gimple_assign_set_rhs3(stmt, rhs3);
#endif
gimple_set_vuse(stmt, gimple_vuse(oldstmt));
gimple_set_vdef(stmt, gimple_vdef(oldstmt));
gsi = gsi_for_stmt(oldstmt);
gsi_insert_after(&gsi, stmt, GSI_SAME_STMT);
update_stmt(stmt);
pointer_set_insert(visited->stmts, oldstmt);
return gimple_assign_lhs(stmt);
}
static tree cast_parm_decl(struct visited *visited, tree phi_ssa_name, tree arg, tree size_overflow_type, basic_block bb)
{
const_gimple assign;
gimple_stmt_iterator gsi;
basic_block first_bb;
gcc_assert(SSA_NAME_IS_DEFAULT_DEF(arg));
if (bb->index == 0) {
first_bb = split_block_after_labels(ENTRY_BLOCK_PTR_FOR_FN(cfun))->dest;
gcc_assert(dom_info_available_p(CDI_DOMINATORS));
set_immediate_dominator(CDI_DOMINATORS, first_bb, ENTRY_BLOCK_PTR_FOR_FN(cfun));
bb = first_bb;
}
gsi = gsi_after_labels(bb);
assign = build_cast_stmt(visited, size_overflow_type, arg, phi_ssa_name, &gsi, BEFORE_STMT, false);
pointer_set_insert(visited->my_stmts, assign);
return get_lhs(assign);
}
static tree use_phi_ssa_name(struct visited *visited, tree ssa_name_var, tree new_arg)
{
gimple_stmt_iterator gsi;
const_gimple assign;
gimple def_stmt = get_def_stmt(new_arg);
if (gimple_code(def_stmt) == GIMPLE_PHI) {
gsi = gsi_after_labels(gimple_bb(def_stmt));
assign = build_cast_stmt(visited, TREE_TYPE(new_arg), new_arg, ssa_name_var, &gsi, BEFORE_STMT, true);
} else {
gsi = gsi_for_stmt(def_stmt);
assign = build_cast_stmt(visited, TREE_TYPE(new_arg), new_arg, ssa_name_var, &gsi, AFTER_STMT, true);
}
pointer_set_insert(visited->my_stmts, assign);
return get_lhs(assign);
}
static tree cast_visited_phi_arg(struct visited *visited, tree ssa_name_var, tree arg, tree size_overflow_type)
{
basic_block bb;
gimple_stmt_iterator gsi;
const_gimple def_stmt;
const_gimple assign;
def_stmt = get_def_stmt(arg);
bb = gimple_bb(def_stmt);
gcc_assert(bb->index != 0);
gsi = gsi_after_labels(bb);
assign = build_cast_stmt(visited, size_overflow_type, arg, ssa_name_var, &gsi, BEFORE_STMT, false);
pointer_set_insert(visited->my_stmts, assign);
return get_lhs(assign);
}
static tree create_new_phi_arg(struct visited *visited, tree ssa_name_var, tree new_arg, gphi *oldstmt, unsigned int i)
{
tree size_overflow_type;
tree arg;
const_gimple def_stmt;
if (new_arg != NULL_TREE && is_gimple_constant(new_arg))
return new_arg;
arg = gimple_phi_arg_def(oldstmt, i);
def_stmt = get_def_stmt(arg);
gcc_assert(def_stmt != NULL);
size_overflow_type = get_size_overflow_type(visited, oldstmt, arg);
switch (gimple_code(def_stmt)) {
case GIMPLE_PHI:
return cast_visited_phi_arg(visited, ssa_name_var, arg, size_overflow_type);
case GIMPLE_NOP: {
basic_block bb;
bb = gimple_phi_arg_edge(oldstmt, i)->src;
return cast_parm_decl(visited, ssa_name_var, arg, size_overflow_type, bb);
}
case GIMPLE_ASM: {
gimple_stmt_iterator gsi;
const_gimple assign;
gimple stmt = get_def_stmt(arg);
gsi = gsi_for_stmt(stmt);
assign = build_cast_stmt(visited, size_overflow_type, arg, ssa_name_var, &gsi, AFTER_STMT, false);
pointer_set_insert(visited->my_stmts, assign);
return get_lhs(assign);
}
default:
gcc_assert(new_arg != NULL_TREE);
gcc_assert(types_compatible_p(TREE_TYPE(new_arg), size_overflow_type));
return use_phi_ssa_name(visited, ssa_name_var, new_arg);
}
}
static gphi *overflow_create_phi_node(struct visited *visited, gphi *oldstmt, tree result)
{
basic_block bb;
gphi *phi;
gimple_seq seq;
gimple_stmt_iterator gsi = gsi_for_stmt(oldstmt);
bb = gsi_bb(gsi);
if (result == NULL_TREE) {
tree old_result = gimple_phi_result(oldstmt);
tree size_overflow_type = get_size_overflow_type(visited, oldstmt, old_result);
result = create_new_var(size_overflow_type);
}
phi = as_a_gphi(create_phi_node(result, bb));
gimple_phi_set_result(phi, make_ssa_name(result, phi));
seq = phi_nodes(bb);
gsi = gsi_last(seq);
gsi_remove(&gsi, false);
gsi = gsi_for_stmt(oldstmt);
gsi_insert_after(&gsi, phi, GSI_NEW_STMT);
gimple_set_bb(phi, bb);
return phi;
}
#if BUILDING_GCC_VERSION <= 4007
static tree create_new_phi_node(struct visited *visited, VEC(tree, heap) **args, tree ssa_name_var, gimple oldstmt)
#else
static tree create_new_phi_node(struct visited *visited, vec<tree, va_heap, vl_embed> *&args, tree ssa_name_var, gphi *oldstmt)
#endif
{
gphi *new_phi;
unsigned int i;
tree arg, result;
location_t loc = gimple_location(oldstmt);
#if BUILDING_GCC_VERSION <= 4007
gcc_assert(!VEC_empty(tree, *args));
#else
gcc_assert(!args->is_empty());
#endif
new_phi = overflow_create_phi_node(visited, oldstmt, ssa_name_var);
result = gimple_phi_result(new_phi);
ssa_name_var = SSA_NAME_VAR(result);
#if BUILDING_GCC_VERSION <= 4007
FOR_EACH_VEC_ELT(tree, *args, i, arg) {
#else
FOR_EACH_VEC_SAFE_ELT(args, i, arg) {
#endif
arg = create_new_phi_arg(visited, ssa_name_var, arg, oldstmt, i);
add_phi_arg(new_phi, arg, gimple_phi_arg_edge(oldstmt, i), loc);
}
#if BUILDING_GCC_VERSION <= 4007
VEC_free(tree, heap, *args);
#else
vec_free(args);
#endif
update_stmt(new_phi);
pointer_set_insert(visited->my_stmts, new_phi);
return result;
}
static tree handle_phi(interesting_stmts_t expand_from, tree orig_result)
{
#if BUILDING_GCC_VERSION <= 4007
VEC(tree, heap) *args = NULL;
#else
vec<tree, va_heap, vl_embed> *args = NULL;
#endif
unsigned int i, len;
tree ssa_name_var = NULL_TREE;
gphi *oldstmt = as_a_gphi(get_def_stmt(orig_result));
len = gimple_phi_num_args(oldstmt);
pointer_set_insert(expand_from->visited->stmts, oldstmt);
for (i = 0; i < len; i++) {
tree arg, new_arg;
arg = gimple_phi_arg_def(oldstmt, i);
new_arg = expand(expand_from, arg);
if (ssa_name_var == NULL_TREE && new_arg != NULL_TREE)
ssa_name_var = SSA_NAME_VAR(new_arg);
if (is_gimple_constant(arg)) {
tree size_overflow_type = get_size_overflow_type(expand_from->visited, oldstmt, arg);
new_arg = cast_a_tree(size_overflow_type, arg);
}
#if BUILDING_GCC_VERSION <= 4007
VEC_safe_push(tree, heap, args, new_arg);
#else
vec_safe_push(args, new_arg);
#endif
}
#if BUILDING_GCC_VERSION <= 4007
return create_new_phi_node(expand_from->visited, &args, ssa_name_var, oldstmt);
#else
return create_new_phi_node(expand_from->visited, args, ssa_name_var, oldstmt);
#endif
}
static tree create_cast_assign(struct visited *visited, gassign *stmt)
{
tree rhs1 = gimple_assign_rhs1(stmt);
tree lhs = gimple_assign_lhs(stmt);
const_tree rhs1_type = TREE_TYPE(rhs1);
const_tree lhs_type = TREE_TYPE(lhs);
if (TYPE_UNSIGNED(rhs1_type) == TYPE_UNSIGNED(lhs_type))
return create_assign(visited, stmt, lhs, AFTER_STMT);
return create_assign(visited, stmt, rhs1, AFTER_STMT);
}
static bool skip_lhs_cast_check(struct visited *visited, const gassign *stmt)
{
const_tree rhs = gimple_assign_rhs1(stmt);
const_gimple def_stmt = get_def_stmt(rhs);
// 3.8.2 kernel/futex_compat.c compat_exit_robust_list(): get_user() 64 ulong -> int (compat_long_t), int max
if (gimple_code(def_stmt) == GIMPLE_ASM)
return true;
if (is_const_plus_unsigned_signed_truncation(rhs)) {
pointer_set_insert(visited->no_cast_check, stmt);
return true;
}
return false;
}
static tree create_string_param(tree string)
{
return build1(ADDR_EXPR, ptr_type_node, string);
}
static void insert_cond(basic_block cond_bb, tree arg, enum tree_code cond_code, tree type_value)
{
gcond *cond_stmt;
gimple_stmt_iterator gsi = gsi_last_bb(cond_bb);
cond_stmt = gimple_build_cond(cond_code, arg, type_value, NULL_TREE, NULL_TREE);
gsi_insert_after(&gsi, cond_stmt, GSI_CONTINUE_LINKING);
update_stmt(cond_stmt);
}
static void insert_cond_result(interesting_stmts_t expand_from, basic_block bb_true, const_gimple stmt, const_tree arg, bool min)
{
gcall *func_stmt;
const_gimple def_stmt;
const_tree loc_line;
tree loc_file, ssa_name, current_func;
expanded_location xloc;
char *ssa_name_buf;
int len;
struct cgraph_edge *edge;
struct cgraph_node *report_node;
int frequency;
gimple_stmt_iterator gsi = gsi_start_bb(bb_true);
def_stmt = get_def_stmt(arg);
if (gimple_has_location(def_stmt))
xloc = expand_location(gimple_location(def_stmt));
else if (gimple_has_location(stmt))
xloc = expand_location(gimple_location(stmt));
else
xloc = expand_location(DECL_SOURCE_LOCATION(current_function_decl));
loc_line = build_int_cstu(unsigned_type_node, xloc.line);
loc_file = build_const_char_string(strlen(xloc.file) + 1, xloc.file);
loc_file = create_string_param(loc_file);
current_func = build_const_char_string(DECL_NAME_LENGTH(current_function_decl) + 1, DECL_NAME_POINTER(current_function_decl));
current_func = create_string_param(current_func);
gcc_assert(DECL_NAME(SSA_NAME_VAR(arg)) != NULL);
call_count++;
len = asprintf(&ssa_name_buf, "%s_%u %s, count: %u, decl: %s; num: %u; context: %s;\n", DECL_NAME_POINTER(SSA_NAME_VAR(arg)), SSA_NAME_VERSION(arg), min ? "min" : "max", call_count, expand_from->next_node->decl_name, expand_from->next_node->num, expand_from->next_node->context);
gcc_assert(len > 0);
ssa_name = build_const_char_string(len + 1, ssa_name_buf);
free(ssa_name_buf);
ssa_name = create_string_param(ssa_name);
// void report_size_overflow(const char *file, unsigned int line, const char *func, const char *ssa_name)
func_stmt = as_a_gcall(gimple_build_call(report_size_overflow_decl, 4, loc_file, loc_line, current_func, ssa_name));
gsi_insert_after(&gsi, func_stmt, GSI_CONTINUE_LINKING);
report_node = cgraph_get_create_node(report_size_overflow_decl);
gcc_assert(report_node);
frequency = compute_call_stmt_bb_frequency(current_function_decl, bb_true);
edge = cgraph_create_edge(get_cnode(current_function_decl), report_node, func_stmt, bb_true->count, frequency, bb_true->loop_depth);
gcc_assert(edge != NULL);
}
static void insert_check_size_overflow(interesting_stmts_t expand_from, gimple stmt, enum tree_code cond_code, tree arg, tree type_value, bool before, bool min)
{
basic_block cond_bb, join_bb, bb_true;
edge e;
gimple_stmt_iterator gsi = gsi_for_stmt(stmt);
cond_bb = gimple_bb(stmt);
if (before)
gsi_prev(&gsi);
if (gsi_end_p(gsi))
e = split_block_after_labels(cond_bb);
else
e = split_block(cond_bb, gsi_stmt(gsi));
cond_bb = e->src;
join_bb = e->dest;
e->flags = EDGE_FALSE_VALUE;
e->probability = REG_BR_PROB_BASE;
bb_true = create_empty_bb(cond_bb);
make_edge(cond_bb, bb_true, EDGE_TRUE_VALUE);
make_edge(cond_bb, join_bb, EDGE_FALSE_VALUE);
make_edge(bb_true, join_bb, EDGE_FALLTHRU);
gcc_assert(dom_info_available_p(CDI_DOMINATORS));
set_immediate_dominator(CDI_DOMINATORS, bb_true, cond_bb);
set_immediate_dominator(CDI_DOMINATORS, join_bb, cond_bb);
if (current_loops != NULL) {
gcc_assert(cond_bb->loop_father == join_bb->loop_father);
add_bb_to_loop(bb_true, cond_bb->loop_father);
}
insert_cond(cond_bb, arg, cond_code, type_value);
insert_cond_result(expand_from, bb_true, stmt, arg, min);
// print_the_code_insertions(stmt);
}
void check_size_overflow(interesting_stmts_t expand_from, gimple stmt, tree size_overflow_type, tree cast_rhs, tree rhs, bool before)
{
const_tree rhs_type = TREE_TYPE(rhs);
tree cast_rhs_type, type_max_type, type_min_type, type_max, type_min;
if (pointer_set_contains(expand_from->visited->no_cast_check, stmt))
return;
gcc_assert(rhs_type != NULL_TREE);
if (TREE_CODE(rhs_type) == POINTER_TYPE)
return;
gcc_assert(TREE_CODE(rhs_type) == INTEGER_TYPE || TREE_CODE(rhs_type) == ENUMERAL_TYPE);
if (is_gimple_assign(stmt) && neg_short_add_intentional_overflow(as_a_gassign(stmt)))
return;
type_max = cast_a_tree(size_overflow_type, TYPE_MAX_VALUE(rhs_type));
// typemax (-1) < typemin (0)
if (TREE_OVERFLOW(type_max))
return;
type_min = cast_a_tree(size_overflow_type, TYPE_MIN_VALUE(rhs_type));
cast_rhs_type = TREE_TYPE(cast_rhs);
type_max_type = TREE_TYPE(type_max);
gcc_assert(types_compatible_p(cast_rhs_type, type_max_type));
insert_check_size_overflow(expand_from, stmt, GT_EXPR, cast_rhs, type_max, before, MAX_CHECK);
// special case: get_size_overflow_type(), 32, u64->s
if (LONG_TYPE_SIZE == GET_MODE_BITSIZE(SImode) && TYPE_UNSIGNED(size_overflow_type) && !TYPE_UNSIGNED(rhs_type))
return;
type_min_type = TREE_TYPE(type_min);
gcc_assert(types_compatible_p(type_max_type, type_min_type));
insert_check_size_overflow(expand_from, stmt, LT_EXPR, cast_rhs, type_min, before, MIN_CHECK);
}
static tree get_my_stmt_lhs(struct visited *visited, gimple stmt)
{
gimple_stmt_iterator gsi;
gimple next_stmt = NULL;
gsi = gsi_for_stmt(stmt);
do {
gsi_next(&gsi);
next_stmt = gsi_stmt(gsi);
if (gimple_code(stmt) == GIMPLE_PHI && !pointer_set_contains(visited->my_stmts, next_stmt))
return NULL_TREE;
if (pointer_set_contains(visited->my_stmts, next_stmt) && !pointer_set_contains(visited->skip_expr_casts, next_stmt))
break;
gcc_assert(pointer_set_contains(visited->my_stmts, next_stmt));
} while (!gsi_end_p(gsi));
gcc_assert(next_stmt);
return get_lhs(next_stmt);
}
/* When the result of the negation is cast to a signed type then move
* the size_overflow cast check before negation.
* ssa:
* unsigned _588
* _588 = _587 >> 12;
* _589 = -_588;
* _590 = (long int) _589;
*/
static bool handle_unsigned_neg_or_bit_not(interesting_stmts_t expand_from, const gassign *stmt)
{
gimple def_neg_stmt, neg_stmt;
tree lhs, new_neg_rhs;
const_tree rhs, neg_rhs;
enum tree_code rhs_code;
rhs = gimple_assign_rhs1(stmt);
lhs = gimple_assign_lhs(stmt);
if (TYPE_UNSIGNED(TREE_TYPE(lhs)) || !TYPE_UNSIGNED(TREE_TYPE(rhs)))
return false;
neg_stmt = get_def_stmt(rhs);
if (!neg_stmt || !is_gimple_assign(neg_stmt))
return false;
rhs_code = gimple_assign_rhs_code(neg_stmt);
if (rhs_code != BIT_NOT_EXPR && rhs_code != NEGATE_EXPR)
return false;
neg_rhs = gimple_assign_rhs1(neg_stmt);
def_neg_stmt = get_def_stmt(neg_rhs);
if (!def_neg_stmt)
return false;
new_neg_rhs = get_my_stmt_lhs(expand_from->visited, def_neg_stmt);
check_size_overflow(expand_from, neg_stmt, TREE_TYPE(new_neg_rhs), new_neg_rhs, lhs, BEFORE_STMT);
pointer_set_insert(expand_from->visited->no_cast_check, stmt);
return true;
}
static tree create_cast_overflow_check(interesting_stmts_t expand_from, tree new_rhs1, gassign *stmt)
{
bool cast_lhs, cast_rhs;
tree lhs = gimple_assign_lhs(stmt);
tree rhs = gimple_assign_rhs1(stmt);
const_tree lhs_type = TREE_TYPE(lhs);
const_tree rhs_type = TREE_TYPE(rhs);
enum machine_mode lhs_mode = TYPE_MODE(lhs_type);
enum machine_mode rhs_mode = TYPE_MODE(rhs_type);
unsigned int lhs_size = GET_MODE_BITSIZE(lhs_mode);
unsigned int rhs_size = GET_MODE_BITSIZE(rhs_mode);
static bool check_lhs[3][4] = {
// ss su us uu
{ false, true, true, false }, // lhs > rhs
{ false, false, false, false }, // lhs = rhs
{ true, true, true, true }, // lhs < rhs
};
static bool check_rhs[3][4] = {
// ss su us uu
{ true, false, true, true }, // lhs > rhs
{ true, false, true, true }, // lhs = rhs
{ true, false, true, true }, // lhs < rhs
};
if (handle_unsigned_neg_or_bit_not(expand_from, stmt))
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
// skip lhs check on HI -> QI cast
if (rhs_mode == HImode && lhs_mode == QImode) {
pointer_set_insert(expand_from->visited->no_cast_check, stmt);
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
}
// skip lhs check on signed SI -> HI cast or signed SI -> QI cast
if (rhs_mode == SImode && !TYPE_UNSIGNED(rhs_type) && (lhs_mode == HImode || lhs_mode == QImode))
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
if (lhs_size > rhs_size) {
cast_lhs = check_lhs[0][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
cast_rhs = check_rhs[0][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
} else if (lhs_size == rhs_size) {
cast_lhs = check_lhs[1][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
cast_rhs = check_rhs[1][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
} else {
cast_lhs = check_lhs[2][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
cast_rhs = check_rhs[2][TYPE_UNSIGNED(rhs_type) + 2 * TYPE_UNSIGNED(lhs_type)];
}
if (!cast_lhs && !cast_rhs)
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
if (cast_lhs && !skip_lhs_cast_check(expand_from->visited, stmt))
check_size_overflow(expand_from, stmt, TREE_TYPE(new_rhs1), new_rhs1, lhs, BEFORE_STMT);
if (cast_rhs)
check_size_overflow(expand_from, stmt, TREE_TYPE(new_rhs1), new_rhs1, rhs, BEFORE_STMT);
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
}
static tree handle_unary_rhs(interesting_stmts_t expand_from, gassign *stmt)
{
enum tree_code rhs_code;
tree rhs1, new_rhs1, lhs = gimple_assign_lhs(stmt);
if (pointer_set_contains(expand_from->visited->my_stmts, stmt))
return lhs;
rhs1 = gimple_assign_rhs1(stmt);
if (TREE_CODE(TREE_TYPE(rhs1)) == POINTER_TYPE)
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
new_rhs1 = expand(expand_from, rhs1);
if (new_rhs1 == NULL_TREE)
return create_cast_assign(expand_from->visited, stmt);
if (pointer_set_contains(expand_from->visited->no_cast_check, stmt))
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
#if BUILDING_GCC_VERSION >= 5000
if (short_or_neg_const_ushort(stmt)) {
pointer_set_insert(expand_from->visited->no_cast_check, stmt);
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
}
#endif
rhs_code = gimple_assign_rhs_code(stmt);
if (rhs_code == BIT_NOT_EXPR || rhs_code == NEGATE_EXPR) {
tree size_overflow_type = get_size_overflow_type(expand_from->visited, stmt, rhs1);
new_rhs1 = cast_to_new_size_overflow_type(expand_from->visited, stmt, new_rhs1, size_overflow_type, BEFORE_STMT);
check_size_overflow(expand_from, stmt, size_overflow_type, new_rhs1, rhs1, BEFORE_STMT);
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
}
if (!gimple_assign_cast_p(stmt))
return dup_assign(expand_from->visited, stmt, lhs, new_rhs1, NULL_TREE, NULL_TREE);
return create_cast_overflow_check(expand_from, new_rhs1, stmt);
}
static tree handle_unary_ops(interesting_stmts_t expand_from, gassign *stmt)
{
tree rhs1, lhs = gimple_assign_lhs(stmt);
gimple def_stmt = get_def_stmt(lhs);
gcc_assert(gimple_code(def_stmt) != GIMPLE_NOP);
rhs1 = gimple_assign_rhs1(def_stmt);
if (is_gimple_constant(rhs1))
return create_assign(expand_from->visited, def_stmt, lhs, AFTER_STMT);
switch (TREE_CODE(rhs1)) {
case SSA_NAME: {
tree ret = handle_unary_rhs(expand_from, as_a_gassign(def_stmt));
if (gimple_assign_cast_p(stmt))
unsigned_signed_cast_intentional_overflow(expand_from->visited, stmt);
return ret;
}
case ARRAY_REF:
case BIT_FIELD_REF:
case ADDR_EXPR:
case COMPONENT_REF:
case INDIRECT_REF:
#if BUILDING_GCC_VERSION >= 4006
case MEM_REF:
#endif
case TARGET_MEM_REF:
case VIEW_CONVERT_EXPR:
return create_assign(expand_from->visited, def_stmt, lhs, AFTER_STMT);
case PARM_DECL:
case VAR_DECL:
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
default:
debug_gimple_stmt(def_stmt);
debug_tree(rhs1);
gcc_unreachable();
}
}
static void __unused print_the_code_insertions(const_gimple stmt)
{
location_t loc = gimple_location(stmt);
inform(loc, "Integer size_overflow check applied here.");
}
static bool is_from_cast(const_tree node)
{
gimple def_stmt = get_def_stmt(node);
if (!def_stmt)
return false;
if (gimple_assign_cast_p(def_stmt))
return true;
return false;
}
// Skip duplication when there is a minus expr and the type of rhs1 or rhs2 is a pointer_type.
static bool is_ptr_diff(gassign *stmt)
{
const_tree rhs1, rhs2, ptr1_rhs, ptr2_rhs;
if (gimple_assign_rhs_code(stmt) != MINUS_EXPR)
return false;
rhs1 = gimple_assign_rhs1(stmt);
if (!is_from_cast(rhs1))
return false;
rhs2 = gimple_assign_rhs2(stmt);
if (!is_from_cast(rhs2))
return false;
ptr1_rhs = gimple_assign_rhs1(get_def_stmt(rhs1));
ptr2_rhs = gimple_assign_rhs1(get_def_stmt(rhs2));
if (TREE_CODE(TREE_TYPE(ptr1_rhs)) != POINTER_TYPE && TREE_CODE(TREE_TYPE(ptr2_rhs)) != POINTER_TYPE)
return false;
return true;
}
static tree handle_comparison_code_class(interesting_stmts_t expand_from, gassign *stmt, tree new_rhs1, tree new_rhs2)
{
tree rhs1, rhs2, lhs;
rhs1 = gimple_assign_rhs1(stmt);
if (!is_gimple_constant(rhs1) && new_rhs1 != NULL_TREE)
check_size_overflow(expand_from, stmt, TREE_TYPE(new_rhs1), new_rhs1, rhs1, BEFORE_STMT);
lhs = gimple_assign_lhs(stmt);
if (new_rhs2 == NULL_TREE)
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
rhs2 = gimple_assign_rhs2(stmt);
if (!is_gimple_constant(rhs2))
check_size_overflow(expand_from, stmt, TREE_TYPE(new_rhs2), new_rhs2, rhs2, BEFORE_STMT);
return create_assign(expand_from->visited, stmt, lhs, AFTER_STMT);
}
static tree handle_binary_ops(interesting_stmts_t expand_from, tree lhs)
{
enum intentional_overflow_type res;
tree rhs1, rhs2, new_lhs;
gassign *def_stmt = as_a_gassign(get_def_stmt(lhs));
tree new_rhs1 = NULL_TREE;
tree new_rhs2 = NULL_TREE;
if (is_ptr_diff(def_stmt))
return create_assign(expand_from->visited, def_stmt, lhs, AFTER_STMT);
rhs1 = gimple_assign_rhs1(def_stmt);
rhs2 = gimple_assign_rhs2(def_stmt);
/* no DImode/TImode division in the 32/64 bit kernel */
switch (gimple_assign_rhs_code(def_stmt)) {
case RDIV_EXPR:
case TRUNC_DIV_EXPR:
case CEIL_DIV_EXPR:
case FLOOR_DIV_EXPR:
case ROUND_DIV_EXPR:
case TRUNC_MOD_EXPR:
case CEIL_MOD_EXPR:
case FLOOR_MOD_EXPR:
case ROUND_MOD_EXPR:
case EXACT_DIV_EXPR:
case POINTER_PLUS_EXPR:
case BIT_AND_EXPR:
return create_assign(expand_from->visited, def_stmt, lhs, AFTER_STMT);
default:
break;
}
new_lhs = handle_integer_truncation(expand_from, lhs);
if (new_lhs != NULL_TREE)
return new_lhs;
if (TREE_CODE(rhs1) == SSA_NAME)
new_rhs1 = expand(expand_from, rhs1);
if (TREE_CODE(rhs2) == SSA_NAME)
new_rhs2 = expand(expand_from, rhs2);
res = add_mul_intentional_overflow(def_stmt);
if (res != NO_INTENTIONAL_OVERFLOW) {
new_lhs = dup_assign(expand_from->visited, def_stmt, lhs, new_rhs1, new_rhs2, NULL_TREE);
insert_cast_expr(expand_from->visited, as_a_gassign(get_def_stmt(new_lhs)), res);
return new_lhs;
}
if (skip_expr_on_double_type(def_stmt)) {
new_lhs = dup_assign(expand_from->visited, def_stmt, lhs, new_rhs1, new_rhs2, NULL_TREE);
insert_cast_expr(expand_from->visited, as_a_gassign(get_def_stmt(new_lhs)), NO_INTENTIONAL_OVERFLOW);
return new_lhs;
}
if (is_a_neg_overflow(def_stmt, rhs2))
return handle_intentional_overflow(expand_from, true, def_stmt, new_rhs1, NULL_TREE);
if (is_a_neg_overflow(def_stmt, rhs1))
return handle_intentional_overflow(expand_from, true, def_stmt, new_rhs2, new_rhs2);
if (is_a_constant_overflow(def_stmt, rhs2))
return handle_intentional_overflow(expand_from, !is_a_cast_and_const_overflow(rhs1), def_stmt, new_rhs1, NULL_TREE);
if (is_a_constant_overflow(def_stmt, rhs1))
return handle_intentional_overflow(expand_from, !is_a_cast_and_const_overflow(rhs2), def_stmt, new_rhs2, new_rhs2);
// the const is between 0 and (signed) MAX
if (is_gimple_constant(rhs1))
new_rhs1 = create_assign(expand_from->visited, def_stmt, rhs1, BEFORE_STMT);
if (is_gimple_constant(rhs2))
new_rhs2 = create_assign(expand_from->visited, def_stmt, rhs2, BEFORE_STMT);
if (TREE_CODE_CLASS(gimple_assign_rhs_code(def_stmt)) == tcc_comparison)
return handle_comparison_code_class(expand_from, def_stmt, new_rhs1, new_rhs2);
if (uconst_neg_intentional_overflow(def_stmt)) {
inform(gimple_location(def_stmt), "%s: gcc intentional overflow", __func__);
gcc_unreachable();
}
return dup_assign(expand_from->visited, def_stmt, lhs, new_rhs1, new_rhs2, NULL_TREE);
}
#if BUILDING_GCC_VERSION >= 4006
static tree get_new_rhs(interesting_stmts_t expand_from, tree size_overflow_type, tree rhs)
{
if (is_gimple_constant(rhs))
return cast_a_tree(size_overflow_type, rhs);
if (TREE_CODE(rhs) != SSA_NAME)
return NULL_TREE;
return expand(expand_from, rhs);
}
static tree handle_ternary_ops(interesting_stmts_t expand_from, tree lhs)
{
tree rhs1, rhs2, rhs3, new_rhs1, new_rhs2, new_rhs3, size_overflow_type;
gassign *def_stmt = as_a_gassign(get_def_stmt(lhs));
size_overflow_type = get_size_overflow_type(expand_from->visited, def_stmt, lhs);
rhs1 = gimple_assign_rhs1(def_stmt);
rhs2 = gimple_assign_rhs2(def_stmt);
rhs3 = gimple_assign_rhs3(def_stmt);
new_rhs1 = get_new_rhs(expand_from, size_overflow_type, rhs1);
new_rhs2 = get_new_rhs(expand_from, size_overflow_type, rhs2);
new_rhs3 = get_new_rhs(expand_from, size_overflow_type, rhs3);
return dup_assign(expand_from->visited, def_stmt, lhs, new_rhs1, new_rhs2, new_rhs3);
}
#endif
static tree expand_visited(struct visited *visited, gimple def_stmt)
{
gimple_stmt_iterator gsi;
enum gimple_code code = gimple_code(def_stmt);
if (code == GIMPLE_ASM)
return NULL_TREE;
gsi = gsi_for_stmt(def_stmt);
gsi_next(&gsi);
if (gimple_code(def_stmt) == GIMPLE_PHI && gsi_end_p(gsi))
return NULL_TREE;
return get_my_stmt_lhs(visited, def_stmt);
}
tree expand(interesting_stmts_t expand_from, tree lhs)
{
gimple def_stmt;
def_stmt = get_def_stmt(lhs);
if (!def_stmt || gimple_code(def_stmt) == GIMPLE_NOP)
return NULL_TREE;
if (pointer_set_contains(expand_from->visited->my_stmts, def_stmt))
return lhs;
if (pointer_set_contains(expand_from->visited->stmts, def_stmt))
return expand_visited(expand_from->visited, def_stmt);
if (is_gimple_constant(lhs))
return NULL_TREE;
if (skip_types(lhs))
return NULL_TREE;
switch (gimple_code(def_stmt)) {
case GIMPLE_PHI:
return handle_phi(expand_from, lhs);
case GIMPLE_CALL:
case GIMPLE_ASM:
if (is_size_overflow_asm(def_stmt))
return expand(expand_from, get_size_overflow_asm_input(as_a_gasm(def_stmt)));
return create_assign(expand_from->visited, def_stmt, lhs, AFTER_STMT);
case GIMPLE_ASSIGN:
switch (gimple_num_ops(def_stmt)) {
case 2:
return handle_unary_ops(expand_from, as_a_gassign(def_stmt));
case 3:
return handle_binary_ops(expand_from, lhs);
#if BUILDING_GCC_VERSION >= 4006
case 4:
return handle_ternary_ops(expand_from, lhs);
#endif
}
default:
debug_gimple_stmt(def_stmt);
error("%s: unknown gimple code", __func__);
gcc_unreachable();
}
}