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

1334 lines
38 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"
#include "libiberty.h"
static void walk_use_def_next_functions(struct walk_use_def_data *use_def_data, tree lhs);
next_interesting_function_t global_next_interesting_function[GLOBAL_NIFN_LEN];
static bool global_changed;
static struct cgraph_node_hook_list *function_insertion_hook_holder;
static struct cgraph_2node_hook_list *node_duplication_hook_holder;
struct cgraph_node *get_cnode(const_tree fndecl)
{
gcc_assert(TREE_CODE(fndecl) == FUNCTION_DECL);
#if BUILDING_GCC_VERSION <= 4005
return cgraph_get_node(CONST_CAST_TREE(fndecl));
#else
return cgraph_get_node(fndecl);
#endif
}
static bool compare_next_interesting_functions(next_interesting_function_t cur_node, const char *decl_name, const char *context, unsigned int num)
{
// Ignore num without a value
if (num != NONE_ARGNUM && cur_node->num != num)
return false;
if (strcmp(cur_node->context, context))
return false;
return !strcmp(cur_node->decl_name, decl_name);
}
// Return the context of vardecl. If it is in a file scope then the context is vardecl_filebasename
static const char* get_vardecl_context(const_tree decl)
{
expanded_location xloc;
char *buf, *path;
const char *bname;
int len;
xloc = expand_location(DECL_SOURCE_LOCATION(decl));
gcc_assert(xloc.file);
path = xstrdup(xloc.file);
bname = lbasename(path);
len = asprintf(&buf, "vardecl_%s", bname);
gcc_assert(len > 0);
return buf;
}
// Return the type name for a function pointer (or "fielddecl" if the type has no name), otherwise either "vardecl" or "fndecl"
const char* get_decl_context(const_tree decl)
{
switch (TREE_CODE(decl)) {
case FUNCTION_DECL:
return "fndecl";
// TODO: Ignore anonymous types for now
case FIELD_DECL:
return get_type_name_from_field(decl);
case VAR_DECL:
if (TREE_PUBLIC(decl) || DECL_EXTERNAL(decl))
return "vardecl";
if (TREE_STATIC(decl) && !TREE_PUBLIC(decl))
return get_vardecl_context(decl);
// ignore local variable
if (!TREE_STATIC(decl) && !DECL_EXTERNAL(decl))
return NULL;
default:
debug_tree(decl);
gcc_unreachable();
}
}
// Find the function with the specified argument in the list
next_interesting_function_t get_global_next_interesting_function_entry(struct fn_raw_data *raw_data)
{
next_interesting_function_t cur_node, head;
gcc_assert(raw_data->hash != NO_HASH);
gcc_assert(raw_data->decl_str);
gcc_assert(raw_data->context);
head = global_next_interesting_function[raw_data->hash];
for (cur_node = head; cur_node; cur_node = cur_node->next) {
if (raw_data->marked != ASM_STMT_SO_MARK && cur_node->marked == ASM_STMT_SO_MARK)
continue;
if (compare_next_interesting_functions(cur_node, raw_data->decl_str, raw_data->context, raw_data->num))
return cur_node;
}
return NULL;
}
next_interesting_function_t get_global_next_interesting_function_entry_with_hash(struct fn_raw_data *raw_data)
{
gcc_assert(raw_data->decl != NULL_TREE);
gcc_assert(raw_data->decl_str);
raw_data->hash = get_decl_hash(raw_data->decl, raw_data->decl_str);
if (raw_data->hash == NO_HASH)
return NULL;
if (!raw_data->context)
raw_data->context = get_decl_context(raw_data->decl);
if (!raw_data->context)
return NULL;
return get_global_next_interesting_function_entry(raw_data);
}
next_interesting_function_t create_new_next_interesting_entry(struct fn_raw_data *raw_data, next_interesting_function_t orig_next_node)
{
next_interesting_function_t new_node;
gcc_assert(raw_data->decl_str);
gcc_assert(raw_data->context);
gcc_assert(raw_data->hash != NO_HASH);
gcc_assert(raw_data->num != NONE_ARGNUM);
gcc_assert(raw_data->based_decl != SO_NONE);
new_node = (next_interesting_function_t)xmalloc(sizeof(*new_node));
new_node->decl_name = xstrdup(raw_data->decl_str);
gcc_assert(raw_data->context);
new_node->context = xstrdup(raw_data->context);
new_node->hash = raw_data->hash;
new_node->num = raw_data->num;
new_node->next = NULL;
new_node->children = NULL;
new_node->marked = raw_data->marked;
new_node->orig_next_node = orig_next_node;
new_node->based_decl = raw_data->based_decl;
return new_node;
}
// Ignore these functions to not explode coverage (+strncmp+fndecl+3+35130+)
static bool temporary_skip_these_functions(struct fn_raw_data *raw_data)
{
gcc_assert(raw_data->hash != NO_HASH);
gcc_assert(raw_data->decl_str);
if (raw_data->hash == 35130 && !strcmp(raw_data->decl_str, "strncmp"))
return true;
if (raw_data->hash == 46193 && !strcmp(raw_data->decl_str, "strnlen"))
return true;
if (raw_data->hash == 43267 && !strcmp(raw_data->decl_str, "strncpy"))
return true;
if (raw_data->hash == 10300 && !strcmp(raw_data->decl_str, "strncpy_from_user"))
return true;
if (raw_data->hash == 26117 && !strcmp(raw_data->decl_str, "memchr"))
return true;
if (raw_data->hash == 16203 && !strcmp(raw_data->decl_str, "memchr_inv"))
return true;
if (raw_data->hash == 24269 && !strcmp(raw_data->decl_str, "memcmp"))
return true;
if (raw_data->hash == 60390 && !strcmp(raw_data->decl_str, "memcpy"))
return true;
if (raw_data->hash == 25040 && !strcmp(raw_data->decl_str, "memmove"))
return true;
if (raw_data->hash == 29763 && !strcmp(raw_data->decl_str, "memset"))
return true;
return false;
}
// Create the main data structure
next_interesting_function_t create_new_next_interesting_decl(struct fn_raw_data *raw_data, next_interesting_function_t orig_next_node)
{
enum tree_code decl_code;
if (raw_data->num == NONE_ARGNUM)
return NULL;
gcc_assert(raw_data->decl != NULL_TREE);
gcc_assert(raw_data->num != NONE_ARGNUM);
gcc_assert(raw_data->decl_str);
decl_code = TREE_CODE(raw_data->decl);
gcc_assert(decl_code == FIELD_DECL || decl_code == FUNCTION_DECL || decl_code == VAR_DECL);
if (is_vararg(raw_data->decl, raw_data->num))
return NULL;
raw_data->hash = get_decl_hash(raw_data->decl, raw_data->decl_str);
if (raw_data->hash == NO_HASH)
return NULL;
if (get_size_overflow_hash_entry_tree(raw_data, DISABLE_SIZE_OVERFLOW))
return NULL;
if (temporary_skip_these_functions(raw_data))
return NULL;
gcc_assert(raw_data->num <= MAX_PARAM);
// Clones must have an orig_next_node
gcc_assert(!made_by_compiler(raw_data->decl) || orig_next_node);
raw_data->context = get_decl_context(raw_data->decl);
if (!raw_data->context)
return NULL;
return create_new_next_interesting_entry(raw_data, orig_next_node);
}
void add_to_global_next_interesting_function(next_interesting_function_t new_entry)
{
next_interesting_function_t cur_global_head, cur_global, cur_global_end = NULL;
// new_entry is appended to the end of a list
new_entry->next = NULL;
cur_global_head = global_next_interesting_function[new_entry->hash];
if (!cur_global_head) {
global_next_interesting_function[new_entry->hash] = new_entry;
return;
}
for (cur_global = cur_global_head; cur_global; cur_global = cur_global->next) {
if (!cur_global->next)
cur_global_end = cur_global;
if (compare_next_interesting_functions(cur_global, new_entry->decl_name, new_entry->context, new_entry->num))
return;
}
gcc_assert(cur_global_end);
cur_global_end->next = new_entry;
}
/* If the interesting function is a clone then find or create its original next_interesting_function_t node
* and add it to global_next_interesting_function
*/
static next_interesting_function_t create_orig_next_node_for_a_clone(struct fn_raw_data *clone_raw_data)
{
struct fn_raw_data orig_raw_data;
next_interesting_function_t orig_next_node;
enum tree_code decl_code;
gcc_assert(clone_raw_data->decl != NULL_TREE);
gcc_assert(clone_raw_data->num != NONE_ARGNUM);
gcc_assert(clone_raw_data->based_decl != SO_NONE);
initialize_raw_data(&orig_raw_data);
orig_raw_data.decl = get_orig_fndecl(clone_raw_data->decl);
if (DECL_BUILT_IN(orig_raw_data.decl) || DECL_BUILT_IN_CLASS(orig_raw_data.decl) == BUILT_IN_NORMAL)
return NULL;
if (made_by_compiler(orig_raw_data.decl))
return NULL;
decl_code = TREE_CODE(orig_raw_data.decl);
if (decl_code == FIELD_DECL || decl_code == VAR_DECL)
orig_raw_data.num = clone_raw_data->num;
else
orig_raw_data.num = get_correct_argnum(clone_raw_data->decl, orig_raw_data.decl, clone_raw_data->num);
// Skip over ISRA.162 parm decls
if (orig_raw_data.num == CANNOT_FIND_ARG)
return NULL;
orig_raw_data.decl_str = get_orig_decl_name(orig_raw_data.decl);
orig_raw_data.marked = NO_SO_MARK;
orig_next_node = get_global_next_interesting_function_entry_with_hash(&orig_raw_data);
if (orig_next_node)
return orig_next_node;
orig_raw_data.marked = clone_raw_data->marked;
orig_raw_data.based_decl = clone_raw_data->based_decl;
orig_next_node = create_new_next_interesting_decl(&orig_raw_data, NULL);
if (!orig_next_node)
return NULL;
add_to_global_next_interesting_function(orig_next_node);
return orig_next_node;
}
// Find or create the next_interesting_function_t node for decl and num
next_interesting_function_t get_and_create_next_node_from_global_next_nodes(struct fn_raw_data *raw_data, next_interesting_function_t orig_next_node)
{
next_interesting_function_t cur_next_cnode;
gcc_assert(raw_data->decl != NULL_TREE);
if (DECL_NAME(raw_data->decl) == NULL_TREE)
return NULL;
raw_data->decl_str = DECL_NAME_POINTER(raw_data->decl);
cur_next_cnode = get_global_next_interesting_function_entry_with_hash(raw_data);
if (cur_next_cnode)
goto out;
if (!orig_next_node && made_by_compiler(raw_data->decl)) {
orig_next_node = create_orig_next_node_for_a_clone(raw_data);
if (!orig_next_node)
return NULL;
}
cur_next_cnode = create_new_next_interesting_decl(raw_data, orig_next_node);
if (!cur_next_cnode)
return NULL;
add_to_global_next_interesting_function(cur_next_cnode);
out:
if (cur_next_cnode->marked != raw_data->marked && cur_next_cnode->marked != NO_SO_MARK)
return cur_next_cnode;
if (raw_data->marked != NO_SO_MARK && cur_next_cnode->marked == NO_SO_MARK)
cur_next_cnode->marked = raw_data->marked;
return cur_next_cnode;
}
static bool has_next_interesting_function_chain_node(next_interesting_function_t next_cnodes_head, struct fn_raw_data *raw_data)
{
next_interesting_function_t cur_node;
gcc_assert(!raw_data->context);
gcc_assert(raw_data->decl_str);
gcc_assert(raw_data->decl != NULL_TREE);
gcc_assert(raw_data->num != NONE_ARGNUM);
raw_data->context = get_decl_context(raw_data->decl);
// Ignore function if there is no context
if (!raw_data->context)
return true;
for (cur_node = next_cnodes_head; cur_node; cur_node = cur_node->next) {
if (compare_next_interesting_functions(cur_node, raw_data->decl_str, raw_data->context, raw_data->num))
return true;
}
return false;
}
static void handle_function(struct walk_use_def_data *use_def_data, tree fndecl, const_tree arg)
{
struct fn_raw_data raw_data;
next_interesting_function_t orig_next_node, new_node;
gcc_assert(fndecl != NULL_TREE);
// ignore builtins to not explode coverage (e.g., memcpy)
if (DECL_BUILT_IN(fndecl) || DECL_BUILT_IN_CLASS(fndecl) == BUILT_IN_NORMAL)
return;
if (get_intentional_attr_type(fndecl) == MARK_TURN_OFF)
return;
initialize_raw_data(&raw_data);
raw_data.decl = fndecl;
raw_data.based_decl = SO_FUNCTION;
raw_data.decl_str = DECL_NAME_POINTER(fndecl);
raw_data.marked = NO_SO_MARK;
// convert arg into its position
if (arg == NULL_TREE)
raw_data.num = 0;
else
raw_data.num = find_arg_number_tree(arg, raw_data.decl);
if (raw_data.num == CANNOT_FIND_ARG)
return;
if (has_next_interesting_function_chain_node(use_def_data->next_cnodes_head, &raw_data))
return;
if (made_by_compiler(raw_data.decl)) {
orig_next_node = create_orig_next_node_for_a_clone(&raw_data);
if (!orig_next_node)
return;
} else
orig_next_node = NULL;
new_node = create_new_next_interesting_decl(&raw_data, orig_next_node);
if (!new_node)
return;
new_node->next = use_def_data->next_cnodes_head;
use_def_data->next_cnodes_head = new_node;
}
static void walk_use_def_next_functions_phi(struct walk_use_def_data *use_def_data, const_tree result)
{
gphi *phi = as_a_gphi(get_def_stmt(result));
unsigned int i, n = gimple_phi_num_args(phi);
pointer_set_insert(use_def_data->visited, phi);
for (i = 0; i < n; i++) {
tree arg = gimple_phi_arg_def(phi, i);
walk_use_def_next_functions(use_def_data, arg);
}
}
static void walk_use_def_next_functions_binary(struct walk_use_def_data *use_def_data, const_tree lhs)
{
gassign *def_stmt = as_a_gassign(get_def_stmt(lhs));
tree rhs1, rhs2;
rhs1 = gimple_assign_rhs1(def_stmt);
rhs2 = gimple_assign_rhs2(def_stmt);
walk_use_def_next_functions(use_def_data, rhs1);
walk_use_def_next_functions(use_def_data, rhs2);
}
static void walk_use_def_next_functions_unary(struct walk_use_def_data *use_def_data, const gassign *stmt)
{
tree rhs1 = gimple_assign_rhs1(stmt);
walk_use_def_next_functions(use_def_data, rhs1);
}
void __weak handle_function_ptr_ret(struct walk_use_def_data *use_def_data __unused, const_tree fn_ptr __unused)
{
}
static void create_and_append_new_next_interesting_field_var_decl(struct walk_use_def_data *use_def_data, struct fn_raw_data *raw_data)
{
next_interesting_function_t new_node;
if (raw_data->decl == NULL_TREE)
return;
if (DECL_NAME(raw_data->decl) == NULL_TREE)
return;
gcc_assert(!raw_data->decl_str);
gcc_assert(raw_data->num == NONE_ARGNUM);
raw_data->decl_str = DECL_NAME_POINTER(raw_data->decl);
raw_data->num = 0;
raw_data->marked = NO_SO_MARK;
new_node = create_new_next_interesting_decl(raw_data, NULL);
if (!new_node)
return;
new_node->next = use_def_data->next_cnodes_head;
use_def_data->next_cnodes_head = new_node;
}
static void handle_struct_fields(struct walk_use_def_data *use_def_data, const_tree node)
{
struct fn_raw_data raw_data;
initialize_raw_data(&raw_data);
switch (TREE_CODE(node)) {
case ARRAY_REF:
#if BUILDING_GCC_VERSION >= 4006
case MEM_REF:
#endif
case INDIRECT_REF:
case COMPONENT_REF:
raw_data.decl = get_ref_field(node);
raw_data.based_decl = SO_FIELD;
break;
// TODO
case BIT_FIELD_REF:
case VIEW_CONVERT_EXPR:
case REALPART_EXPR:
case IMAGPART_EXPR:
return;
default:
debug_tree(node);
gcc_unreachable();
}
if (get_intentional_attr_type(raw_data.decl) == MARK_TURN_OFF)
return;
create_and_append_new_next_interesting_field_var_decl(use_def_data, &raw_data);
}
static void handle_vardecl(struct walk_use_def_data *use_def_data, tree node)
{
struct fn_raw_data raw_data;
initialize_raw_data(&raw_data);
raw_data.decl = node;
raw_data.based_decl = SO_VAR;
create_and_append_new_next_interesting_field_var_decl(use_def_data, &raw_data);
}
/* Find all functions that influence lhs
*
* Encountered functions are added to the children vector (next_interesting_function_t).
*/
static void walk_use_def_next_functions(struct walk_use_def_data *use_def_data, tree lhs)
{
enum tree_code code;
const_gimple def_stmt;
if (skip_types(lhs))
return;
if (VAR_P(lhs)) {
handle_vardecl(use_def_data, lhs);
return;
}
code = TREE_CODE(lhs);
if (code == PARM_DECL) {
handle_function(use_def_data, current_function_decl, lhs);
return;
}
if (TREE_CODE_CLASS(code) == tcc_reference) {
handle_struct_fields(use_def_data, lhs);
return;
}
if (code != SSA_NAME)
return;
def_stmt = get_def_stmt(lhs);
if (!def_stmt)
return;
if (pointer_set_insert(use_def_data->visited, def_stmt))
return;
switch (gimple_code(def_stmt)) {
case GIMPLE_NOP:
walk_use_def_next_functions(use_def_data, SSA_NAME_VAR(lhs));
return;
case GIMPLE_ASM:
if (!is_size_overflow_asm(def_stmt))
return;
walk_use_def_next_functions(use_def_data, get_size_overflow_asm_input(as_a_const_gasm(def_stmt)));
return;
case GIMPLE_CALL: {
tree fndecl = gimple_call_fndecl(def_stmt);
if (fndecl != NULL_TREE) {
handle_function(use_def_data, fndecl, NULL_TREE);
return;
}
fndecl = gimple_call_fn(def_stmt);
handle_function_ptr_ret(use_def_data, fndecl);
return;
}
case GIMPLE_PHI:
walk_use_def_next_functions_phi(use_def_data, lhs);
return;
case GIMPLE_ASSIGN:
switch (gimple_num_ops(def_stmt)) {
case 2:
walk_use_def_next_functions_unary(use_def_data, as_a_const_gassign(def_stmt));
return;
case 3:
walk_use_def_next_functions_binary(use_def_data, lhs);
return;
}
default:
debug_gimple_stmt((gimple)def_stmt);
error("%s: unknown gimple code", __func__);
gcc_unreachable();
}
}
// Start the search for next_interesting_function_t children based on the (next_interesting_function_t) parent node
static next_interesting_function_t search_next_functions(tree node, next_interesting_function_t parent)
{
struct walk_use_def_data use_def_data;
use_def_data.parent = parent;
use_def_data.next_cnodes_head = NULL;
use_def_data.visited = pointer_set_create();
walk_use_def_next_functions(&use_def_data, node);
pointer_set_destroy(use_def_data.visited);
return use_def_data.next_cnodes_head;
}
// True if child already exists in the next_interesting_function_t children vector
bool has_next_interesting_function_vec(next_interesting_function_t target, next_interesting_function_t next_node)
{
unsigned int i;
next_interesting_function_t cur;
gcc_assert(next_node);
// handle recursion
if (!strcmp(target->decl_name, next_node->decl_name) && target->num == next_node->num)
return true;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, target->children))
return false;
FOR_EACH_VEC_ELT(next_interesting_function_t, target->children, i, cur) {
#else
FOR_EACH_VEC_SAFE_ELT(target->children, i, cur) {
#endif
if (compare_next_interesting_functions(cur, next_node->decl_name, next_node->context, next_node->num))
return true;
}
return false;
}
void push_child(next_interesting_function_t parent, next_interesting_function_t child)
{
if (!has_next_interesting_function_vec(parent, child)) {
#if BUILDING_GCC_VERSION <= 4007
VEC_safe_push(next_interesting_function_t, heap, parent->children, child);
#else
vec_safe_push(parent->children, child);
#endif
}
}
void __weak check_local_variables(next_interesting_function_t next_node __unused) {}
// Add children to parent and global_next_interesting_function
static void collect_data_for_execute(next_interesting_function_t parent, next_interesting_function_t children)
{
next_interesting_function_t cur = children;
gcc_assert(parent);
while (cur) {
struct fn_raw_data child_raw_data;
next_interesting_function_t next, child;
next = cur->next;
initialize_raw_data(&child_raw_data);
child_raw_data.decl_str = cur->decl_name;
child_raw_data.context = cur->context;
child_raw_data.hash = cur->hash;
child_raw_data.num = cur->num;
child_raw_data.marked = NO_SO_MARK;
child = get_global_next_interesting_function_entry(&child_raw_data);
if (!child) {
add_to_global_next_interesting_function(cur);
child = cur;
}
check_local_variables(child);
push_child(parent, child);
cur = next;
}
check_local_variables(parent);
}
next_interesting_function_t __weak get_and_create_next_node_from_global_next_nodes_fnptr(const_tree fn_ptr __unused, struct fn_raw_data *raw_data __unused)
{
return NULL;
}
static next_interesting_function_t create_parent_next_cnode(const_gimple stmt, unsigned int num)
{
struct fn_raw_data raw_data;
initialize_raw_data(&raw_data);
raw_data.num = num;
raw_data.marked = NO_SO_MARK;
raw_data.based_decl = SO_FUNCTION;
switch (gimple_code(stmt)) {
case GIMPLE_ASM:
raw_data.decl = current_function_decl;
raw_data.marked = ASM_STMT_SO_MARK;
return get_and_create_next_node_from_global_next_nodes(&raw_data, NULL);
case GIMPLE_CALL:
raw_data.decl = gimple_call_fndecl(stmt);
if (raw_data.decl != NULL_TREE)
return get_and_create_next_node_from_global_next_nodes(&raw_data, NULL);
raw_data.decl = gimple_call_fn(stmt);
return get_and_create_next_node_from_global_next_nodes_fnptr(raw_data.decl, &raw_data);
case GIMPLE_RETURN:
raw_data.decl = current_function_decl;
return get_and_create_next_node_from_global_next_nodes(&raw_data, NULL);
case GIMPLE_ASSIGN: {
tree lhs = gimple_assign_lhs(stmt);
if (VAR_P(lhs))
raw_data.decl = lhs;
else
raw_data.decl = get_ref_field(lhs);
if (raw_data.decl == NULL_TREE)
return NULL;
return get_and_create_next_node_from_global_next_nodes(&raw_data, NULL);
}
default:
debug_gimple_stmt((gimple)stmt);
gcc_unreachable();
}
}
// Handle potential next_interesting_function_t parent if its argument has an integer type
static void collect_all_possible_size_overflow_fns(const_gimple stmt, tree start_var, unsigned int num)
{
next_interesting_function_t children_next_cnode, parent_next_cnode;
// skip void return values
if (start_var == NULL_TREE)
return;
if (skip_types(start_var))
return;
// handle intentional MARK_TURN_OFF
if (check_intentional_size_overflow_asm_and_attribute(start_var) == MARK_TURN_OFF)
return;
parent_next_cnode = create_parent_next_cnode(stmt, num);
if (!parent_next_cnode)
return;
children_next_cnode = search_next_functions(start_var, parent_next_cnode);
collect_data_for_execute(parent_next_cnode, children_next_cnode);
}
static void collect_all_possible_size_overflow_fields_and_vars(const gassign *assign)
{
tree start_var, decl, lhs = gimple_assign_lhs(assign);
if (VAR_P(lhs))
decl = lhs;
else
decl = get_ref_field(lhs);
if (decl == NULL_TREE)
return;
if (get_intentional_attr_type(decl) == MARK_TURN_OFF)
return;
start_var = gimple_assign_rhs1(assign);
collect_all_possible_size_overflow_fns(assign, start_var, 0);
start_var = gimple_assign_rhs2(assign);
collect_all_possible_size_overflow_fns(assign, start_var, 0);
#if BUILDING_GCC_VERSION >= 4006
start_var = gimple_assign_rhs3(assign);
collect_all_possible_size_overflow_fns(assign, start_var, 0);
#endif
}
// Find potential next_interesting_function_t parents
static void handle_cgraph_node(struct cgraph_node *node)
{
basic_block bb;
tree cur_fndecl = NODE_DECL(node);
set_current_function_decl(cur_fndecl);
FOR_EACH_BB_FN(bb, cfun) {
gimple_stmt_iterator gsi;
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
tree start_var;
gimple stmt = gsi_stmt(gsi);
switch (gimple_code(stmt)) {
case GIMPLE_RETURN: {
const greturn *return_stmt = as_a_const_greturn(stmt);
start_var = gimple_return_retval(return_stmt);
collect_all_possible_size_overflow_fns(return_stmt, start_var, 0);
break;
}
case GIMPLE_ASM: {
const gasm *asm_stmt = as_a_const_gasm(stmt);
if (!is_size_overflow_insert_check_asm(asm_stmt))
break;
start_var = get_size_overflow_asm_input(asm_stmt);
collect_all_possible_size_overflow_fns(asm_stmt, start_var, 0);
break;
}
case GIMPLE_CALL: {
unsigned int i, len;
const gcall *call = as_a_const_gcall(stmt);
tree fndecl = gimple_call_fndecl(call);
if (fndecl != NULL_TREE && (DECL_BUILT_IN(fndecl) || DECL_BUILT_IN_CLASS(fndecl) == BUILT_IN_NORMAL))
break;
len = gimple_call_num_args(call);
for (i = 0; i < len; i++) {
start_var = gimple_call_arg(call, i);
collect_all_possible_size_overflow_fns(call, start_var, i + 1);
}
break;
}
case GIMPLE_ASSIGN:
collect_all_possible_size_overflow_fields_and_vars(as_a_const_gassign(stmt));
break;
default:
break;
}
}
}
unset_current_function_decl();
}
/* Collect all potentially interesting function parameters and return values of integer types
* and store their data flow dependencies
*/
static void size_overflow_generate_summary(void)
{
struct cgraph_node *node;
size_overflow_register_hooks();
FOR_EACH_FUNCTION(node) {
if (is_valid_cgraph_node(node))
handle_cgraph_node(node);
}
}
static void size_overflow_function_insertion_hook(struct cgraph_node *node __unused, void *data __unused)
{
debug_cgraph_node(node);
gcc_unreachable();
}
/* Handle dst if src is in the global_next_interesting_function list.
* If src is a clone then dst inherits the orig_next_node of src otherwise
* src will become the orig_next_node of dst.
*/
static void size_overflow_node_duplication_hook(struct cgraph_node *src, struct cgraph_node *dst, void *data __unused)
{
next_interesting_function_t head, cur;
struct fn_raw_data src_raw_data;
initialize_raw_data(&src_raw_data);
src_raw_data.decl = NODE_DECL(src);
src_raw_data.decl_str = DECL_NAME_POINTER(src_raw_data.decl);
src_raw_data.context = get_decl_context(src_raw_data.decl);
if (!src_raw_data.context)
return;
src_raw_data.num = NONE_ARGNUM;
src_raw_data.marked = NO_SO_MARK;
head = get_global_next_interesting_function_entry_with_hash(&src_raw_data);
if (!head)
return;
for (cur = head; cur; cur = cur->next) {
struct fn_raw_data dst_raw_data;
next_interesting_function_t orig_next_node, next_node;
if (!compare_next_interesting_functions(cur, src_raw_data.decl_str, src_raw_data.context, src_raw_data.num))
continue;
initialize_raw_data(&dst_raw_data);
dst_raw_data.decl = NODE_DECL(dst);
dst_raw_data.decl_str = cgraph_node_name(dst);
dst_raw_data.marked = cur->marked;
dst_raw_data.based_decl = cur->based_decl;
if (!made_by_compiler(dst_raw_data.decl))
break;
// For clones use the original node instead
if (cur->orig_next_node)
orig_next_node = cur->orig_next_node;
else
orig_next_node = cur;
dst_raw_data.num = get_correct_argnum_fndecl(src_raw_data.decl, dst_raw_data.decl, cur->num);
if (dst_raw_data.num == CANNOT_FIND_ARG)
continue;
next_node = create_new_next_interesting_decl(&dst_raw_data, orig_next_node);
if (next_node)
add_to_global_next_interesting_function(next_node);
}
}
void size_overflow_register_hooks(void)
{
static bool init_p = false;
if (init_p)
return;
init_p = true;
function_insertion_hook_holder = cgraph_add_function_insertion_hook(&size_overflow_function_insertion_hook, NULL);
node_duplication_hook_holder = cgraph_add_node_duplication_hook(&size_overflow_node_duplication_hook, NULL);
}
static void set_yes_so_mark(next_interesting_function_t next_node)
{
if (next_node->marked == NO_SO_MARK) {
next_node->marked = YES_SO_MARK;
global_changed = true;
}
// Mark the orig decl as well if it's a clone
if (next_node->orig_next_node && next_node->orig_next_node->marked == NO_SO_MARK) {
next_node->orig_next_node->marked = YES_SO_MARK;
global_changed = true;
}
}
// Determine whether node or orig node is part of a tracked data flow
static bool marked_fn(next_interesting_function_t next_node)
{
bool is_marked_fn, is_marked_orig = false;
is_marked_fn = next_node->marked != NO_SO_MARK;
if (next_node->orig_next_node)
is_marked_orig = next_node->orig_next_node->marked != NO_SO_MARK;
return is_marked_fn || is_marked_orig;
}
// Determine whether node or orig node is in the hash table already
static bool already_in_the_hashtable(next_interesting_function_t next_node)
{
struct fn_raw_data raw_data;
if (next_node->orig_next_node)
next_node = next_node->orig_next_node;
initialize_raw_data(&raw_data);
raw_data.context = next_node->context;
raw_data.orig_decl_str = next_node->decl_name;
raw_data.orig_num = next_node->num;
raw_data.hash = next_node->hash;
return get_size_overflow_hash_entry(&raw_data) != NULL;
}
// Propagate the size_overflow marks up the use-def chains
static bool has_marked_child(next_interesting_function_t next_node)
{
bool ret = false;
unsigned int i;
next_interesting_function_t child;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, next_node->children))
return false;
FOR_EACH_VEC_ELT(next_interesting_function_t, next_node->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(next_node->children, i, child) {
#endif
if (marked_fn(child) || already_in_the_hashtable(child))
ret = true;
}
return ret;
}
/* Set YES_SO_MARK on the function, its orig node and children if:
* * the function or its orig node or one of its children is in the hash table already
* * the function's orig node is marked with YES_SO_MARK or ASM_STMT_SO_MARK
* * one of the children is marked with YES_SO_MARK or ASM_STMT_SO_MARK
*/
static bool set_fn_so_mark(next_interesting_function_t next_node)
{
bool so_fn, so_hashtable, so_child;
so_hashtable = already_in_the_hashtable(next_node);
so_fn = marked_fn(next_node);
so_child = has_marked_child(next_node);
if (!so_fn && !so_hashtable && !so_child)
return false;
set_yes_so_mark(next_node);
return true;
}
// Determine if any of the function pointer targets have data flow between the return value and one of the arguments
static next_interesting_function_t get_same_not_ret_child(next_interesting_function_t parent)
{
unsigned int i;
next_interesting_function_t child;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return NULL;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
if (child->num == 0)
continue;
if (strcmp(parent->decl_name, child->decl_name))
continue;
if (!strcmp(child->context, "fndecl"))
return child;
}
return NULL;
}
/* Trace a return value of function pointer type back to an argument via a concrete function
fnptr 0 && fn 0 && (fn 0 -> fn 2) => fnptr 2 */
static void search_missing_fptr_arg(next_interesting_function_t parent)
{
next_interesting_function_t child;
unsigned int i;
#if BUILDING_GCC_VERSION <= 4007
VEC(next_interesting_function_t, heap) *new_children = NULL;
#else
vec<next_interesting_function_t, va_heap, vl_embed> *new_children = NULL;
#endif
if (parent->num != 0)
return;
if (!strcmp(parent->context, "fndecl"))
return;
if (!strncmp(parent->context, "vardecl", sizeof("vardecl") - 1))
return;
// fnptr 0 && fn 0
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
next_interesting_function_t cur_next_node, tracked_fn;
if (child->num != 0)
continue;
// (fn 0 -> fn 2)
tracked_fn = get_same_not_ret_child(child);
if (!tracked_fn)
continue;
// fn 2 => fnptr 2
for (cur_next_node = global_next_interesting_function[parent->hash]; cur_next_node; cur_next_node = cur_next_node->next) {
if (cur_next_node->num != tracked_fn->num)
continue;
if (strcmp(parent->decl_name, cur_next_node->decl_name))
continue;
if (!has_next_interesting_function_vec(parent, cur_next_node)) {
#if BUILDING_GCC_VERSION <= 4007
VEC_safe_push(next_interesting_function_t, heap, new_children, cur_next_node);
#else
vec_safe_push(new_children, cur_next_node);
#endif
}
}
}
#if BUILDING_GCC_VERSION == 4005
if (VEC_empty(next_interesting_function_t, new_children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, new_children, i, child)
VEC_safe_push(next_interesting_function_t, heap, parent->children, child);
#elif BUILDING_GCC_VERSION <= 4007
VEC_safe_splice(next_interesting_function_t, heap, parent->children, new_children);
#else
vec_safe_splice(parent->children, new_children);
#endif
}
static void walk_so_marked_fns(next_interesting_function_set *visited, next_interesting_function_t parent)
{
unsigned int i;
next_interesting_function_t child;
gcc_assert(parent);
if (!set_fn_so_mark(parent))
return;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
set_yes_so_mark(child);
if (!pointer_set_insert(visited, child))
walk_so_marked_fns(visited, child);
}
}
// Do a depth-first recursive dump of the next_interesting_function_t children vector
static void print_missing_functions(next_interesting_function_set *visited, next_interesting_function_t parent)
{
unsigned int i;
next_interesting_function_t child;
gcc_assert(parent);
gcc_assert(parent->marked != NO_SO_MARK);
print_missing_function(parent);
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
gcc_assert(child->marked != NO_SO_MARK);
if (!pointer_set_insert(visited, child))
print_missing_functions(visited, child);
}
}
// Set YES_SO_MARK on functions that will be emitted into the hash table
static void search_so_marked_fns(void)
{
unsigned int i;
next_interesting_function_set *visited;
visited = next_interesting_function_pointer_set_create();
for (i = 0; i < GLOBAL_NIFN_LEN; i++) {
next_interesting_function_t cur_global;
for (cur_global = global_next_interesting_function[i]; cur_global; cur_global = cur_global->next) {
if (cur_global->marked != NO_SO_MARK && !pointer_set_insert(visited, cur_global))
walk_so_marked_fns(visited, cur_global);
}
}
pointer_set_destroy(visited);
}
static void walk_marked_functions(next_interesting_function_set *visited, next_interesting_function_t parent)
{
unsigned int i;
next_interesting_function_t child;
if (pointer_set_insert(visited, parent))
return;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
switch (parent->based_decl) {
case SO_FIELD:
child->based_decl = SO_FIELD;
gcc_assert(child->based_decl != SO_FUNCTION_POINTER);
break;
case SO_FUNCTION_POINTER:
child->based_decl = SO_FUNCTION_POINTER;
gcc_assert(child->based_decl != SO_FIELD);
break;
case SO_FUNCTION:
case SO_VAR:
break;
default:
gcc_unreachable();
}
walk_marked_functions(visited, child);
}
}
static void set_based_decl(void)
{
unsigned int i;
next_interesting_function_set *visited;
visited = next_interesting_function_pointer_set_create();
for (i = 0; i < GLOBAL_NIFN_LEN; i++) {
next_interesting_function_t cur;
for (cur = global_next_interesting_function[i]; cur; cur = cur->next) {
if (cur->marked == ASM_STMT_SO_MARK && !pointer_set_contains(visited, cur))
walk_marked_functions(visited, cur);
}
}
pointer_set_destroy(visited);
}
// Print functions missing from the hash table
static void print_so_marked_fns(void)
{
unsigned int i;
next_interesting_function_set *visited;
visited = next_interesting_function_pointer_set_create();
for (i = 0; i < GLOBAL_NIFN_LEN; i++) {
next_interesting_function_t cur_global;
for (cur_global = global_next_interesting_function[i]; cur_global; cur_global = cur_global->next) {
if (cur_global->marked != NO_SO_MARK && !pointer_set_insert(visited, cur_global))
print_missing_functions(visited, cur_global);
}
}
pointer_set_destroy(visited);
}
void __weak check_global_variables(next_interesting_function_t cur_global __unused) {}
static void global_vars_and_fptrs(void)
{
unsigned int i;
if (!in_lto_p)
return;
// Collect vardecls and funtions reachable by function pointers
for (i = 0; i < GLOBAL_NIFN_LEN; i++) {
next_interesting_function_t cur_global;
for (cur_global = global_next_interesting_function[i]; cur_global; cur_global = cur_global->next) {
check_global_variables(cur_global);
search_missing_fptr_arg(cur_global);
}
}
}
static void print_parent_child(next_interesting_function_set *visited, next_interesting_function_t parent)
{
unsigned int i;
next_interesting_function_t child;
#if BUILDING_GCC_VERSION <= 4007
if (VEC_empty(next_interesting_function_t, parent->children))
return;
FOR_EACH_VEC_ELT(next_interesting_function_t, parent->children, i, child) {
#else
FOR_EACH_VEC_SAFE_ELT(parent->children, i, child) {
#endif
fprintf(stderr, " PARENT: decl: %s-%u context: %s %p\n", parent->decl_name, parent->num, parent->context, parent);
fprintf(stderr, " \tCHILD: decl: %s-%u context: %s %p\n", child->decl_name, child->num, child->context, child);
if (!pointer_set_insert(visited, child))
print_parent_child(visited, child);
}
}
static void print_data_flow(void)
{
unsigned int i;
next_interesting_function_set *visited;
if (!in_lto_p)
return;
visited = next_interesting_function_pointer_set_create();
for (i = 0; i < GLOBAL_NIFN_LEN; i++) {
next_interesting_function_t cur_global;
for (cur_global = global_next_interesting_function[i]; cur_global; cur_global = cur_global->next) {
if (cur_global->marked == NO_SO_MARK || pointer_set_insert(visited, cur_global))
continue;
fprintf(stderr, "Data flow: decl: %s-%u context: %s %p\n", cur_global->decl_name, cur_global->num, cur_global->context, cur_global);
print_parent_child(visited, cur_global);
fprintf(stderr, "\n");
}
}
pointer_set_destroy(visited);
}
static void set_so_fns(void)
{
do {
global_changed = false;
search_so_marked_fns();
} while (global_changed);
print_data_flow();
}
// Print all missing interesting functions
static unsigned int size_overflow_execute(void)
{
if (flag_lto && !in_lto_p)
return 0;
global_vars_and_fptrs();
set_so_fns();
set_based_decl();
print_so_marked_fns();
if (in_lto_p) {
fprintf(stderr, "%s: SIZE_OVERFLOW EXECUTE\n", __func__);
print_global_next_interesting_functions();
}
return 0;
}
// Omit the IPA/LTO callbacks until https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61311 gets fixed (license concerns)
#if BUILDING_GCC_VERSION >= 4008
void __weak size_overflow_write_summary(void) {}
void __weak size_overflow_write_optimization_summary(void) {}
#elif BUILDING_GCC_VERSION >= 4006
void __weak size_overflow_write_summary(cgraph_node_set set __unused, varpool_node_set vset __unused) {}
void __weak size_overflow_write_optimization_summary(cgraph_node_set set __unused, varpool_node_set vset __unused) {}
#else
void __weak size_overflow_write_summary(cgraph_node_set set __unused) {}
void __weak size_overflow_write_optimization_summary(cgraph_node_set set __unused) {}
#endif
void __weak size_overflow_read_summary(void);
void __weak size_overflow_read_optimization_summary(void);
#define PASS_NAME size_overflow
#define NO_STMT_FIXUP
#define NO_VARIABLE_TRANSFORM
#define NO_GATE
#include "gcc-generate-ipa-pass.h"