blob: ef82e535512233ea5b69b436ab55eed5e9584bc6 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* Includes */
#include "gcc-common.h"
#include "cache.h"
/* Macros */
#define STAGE1_INT "/tmp/rai_int"
#define STAGE1_CHK "/tmp/rai_chk"
#define STAGE2_FINAL "/tmp/rai_final"
#define LINELEN 100
#define STAGE1_PG_COUNT 30
#define STAGE2_PG_COUNT 60
#define __init ".init.text"
#define __initdata ".init.data"
#define __ro_after_init ".data..ro_after_init"
#define IS_RECORD_TYPE(T) (TREE_CODE(T) == RECORD_TYPE)
#define FOR_EACH_STRUCT_MEMBER(T, MEM) \
for (MEM = TYPE_VALUES((T)); (MEM); MEM = TREE_CHAIN(MEM))
#define FOR_EACH_ATTR_VALUE(SEC, AV) \
for (AV = TREE_VALUE((SEC)); (AV); AV = TREE_CHAIN(AV))
#define PASS_NAME roafteriniter
#define NO_WRITE_SUMMARY
#define NO_GENERATE_SUMMARY
#define NO_READ_SUMMARY
#define NO_READ_OPTIMIZATION_SUMMARY
#define NO_WRITE_OPTIMIZATION_SUMMARY
#define NO_STMT_FIXUP
#define NO_FUNCTION_TRANSFORM
#define NO_VARIABLE_TRANSFORM
#define NO_GATE
/* GCC Callbacks */
__visible int plugin_init(struct plugin_name_args *,
struct plugin_gcc_version *);
static unsigned int roafteriniter_execute(void);
static void rai_callback_finish(void *, void *);
/* Local GCC helper include(Needs to be placed after some macros,
* callbacks).
*/
#include "gcc-generate-gimple-pass.h"
/* Static declarations */
static struct cache interesting, checked, results;
static const char *blacklisted_typenames[] = {
"atomic_t",
"atomic64_t",
"arch_spinlock_t",
"spinlock_t",
"cpumask_t",
"sk_buff_head",
NULL
};
static bool stage2 = false;
static void die(const char *format, ...);
static void rai_map_cache(void);
static const char *rai_structtype_str(tree);
static bool rai_check_interesting_sttype(tree, const char *);
static bool rai_hardcoded_blacklist_typename(const char *);
static bool rai_interesting_struct_type(tree);
static void rai_stage1_execute(void);
static bool is_global(tree);
static bool is_fn_annotated(tree);
static bool is_var_annotated(tree);
static void rai_check_assign_stmt(gimple);
static void rai_stage2_execute(void);
/* Externs */
__visible int plugin_is_GPL_compatible = 1;
__visible int plugin_init(struct plugin_name_args *info,
struct plugin_gcc_version *ver __unused)
{
int i;
const char *plugin_name = info->base_name;
const int argc = info->argc;
struct plugin_argument *argv = info->argv;
PASS_INFO(roafteriniter, "ssa", 1, PASS_POS_INSERT_AFTER);
for (i = 0; i < argc; i++) {
if (!(strcmp(argv[i].key, "stage2"))) {
stage2 = true;
continue;
}
fprintf(stderr, "unknown plugin option(%s)\n", argv[i].key);
return -1;
}
/* Map cache */
rai_map_cache();
register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
&roafteriniter_pass_info);
register_callback(plugin_name, PLUGIN_FINISH, rai_callback_finish,
NULL);
return 0;
}
/* Static definitions */
static void die(const char *format, ...)
{
va_list vargs;
va_start(vargs, format);
fprintf(stderr, "FAILURE: ");
vfprintf(stderr, format, vargs);
fprintf(stderr, "\n");
va_end(vargs);
exit(-1);
}
/*
* rai_map_cache - Map cache files into memory.
*
* The following cache files are mapped into memory:
* - STAGE1_INT : Cache holding struct types that are considered interesting.
* - STAGE1_CHK : Cache holding struct types that have been checked for whether they are interesting
* or not.
* - STAGE2_FINAL : Cache holding log entries corresponding to writes to instances of interesting struct
* types.
*/
static void rai_map_cache(void)
{
int ret;
if ((ret = cache_map(&interesting, STAGE1_INT, "/int", STAGE1_PG_COUNT*0x1000) != CACHE_OP_SUCCESS))
die("cache_map() returned %d at %d", ret, __LINE__);
if (!stage2 &&
(ret = cache_map(&checked, STAGE1_CHK, "/chk", STAGE1_PG_COUNT*0x1000) != CACHE_OP_SUCCESS))
die("cache_map() returned %d at %d", ret, __LINE__);
if (stage2 &&
(ret = cache_map(&results, STAGE2_FINAL, "/final", STAGE2_PG_COUNT*0x1000) != CACHE_OP_SUCCESS))
die("cache_map() returned %d at %d", ret, __LINE__);
}
/*
* rai_structtype_str - Return the name of the struct type.
*
* @type_tree: tree node corresponding to the RECORD type or struct instance.
*
* Returns:
* const char* representing struct instance name or struct type name. If the
* typename is a typedef, return the typedef.
*/
static const char *rai_structtype_str(tree type_tree)
{
tree name_tree;
if (!type_tree)
return NULL;
if (!(name_tree = TYPE_NAME(type_tree)))
return NULL;
if (TREE_CODE(name_tree) == IDENTIFIER_NODE)
return IDENTIFIER_POINTER(name_tree);
else if (TREE_CODE(name_tree) == TYPE_DECL && DECL_NAME(name_tree))
return IDENTIFIER_POINTER(DECL_NAME(name_tree));
return NULL;
}
/*
* rai_check_interesting_sttype - Check if a struct type is interesting.
*
* The following struct types will be considered not interesting given that
* `struct A` is not interesting.
*
* struct B {
* struct A a; // non-interesting as `struct A` is non-interesting.
* };
* struct C {
* struct A *a_ptr; // non-interesting as `struct A` is non-interesting.
* };
* struct D {
* struct B *b; // non-interesting as `struct B` is non-interesting.
* };
* struct E {
* struct C c; // non-interesting as `struct C` is non-interesting.
* };
* struct F {
* struct F *f; // non-interesting as contains pointer to its own type.
* };
*
* @type_tree: tree node corresponding to a struct type.
* @type_name: string representation of the struct type name.
*/
static bool rai_check_interesting_sttype(tree type_tree, const char *type_name)
{
tree member;
bool has_fields = false;
gcc_assert(TREE_CODE(type_tree) == RECORD_TYPE);
/* (1) Iterate through each member of the struct type. Investigate each
* member for properties that might make this struct type non-interesting. */
FOR_EACH_STRUCT_MEMBER(type_tree, member) {
has_fields = true;
gcc_assert(TREE_CODE(member) == FIELD_DECL);
/* (1.1) Check if the member is a pointer to something. */
tree member_tree = TREE_TYPE(member);
if (TREE_CODE(member_tree) == POINTER_TYPE) {
/* (1.1.1) If it is a pointer to a non-RECORD type, we cannot
* infer anything useful from it. So continue onto the next member. */
tree member_type = TREE_TYPE(member_tree);
if (TREE_CODE(member_type) != RECORD_TYPE)
continue;
/* (1.1.2) If it is an anonymous struct, we cannot infer anything useful
* from it. So continue onto the next member. */
const char *member_typename = rai_structtype_str(member_type);
if (!member_typename)
continue;
/* (1.1.3) If it is a pointer to a RECORD type, check if it is a pointer
* to itself. If so, this struct type is deemed as not interesting. */
/* (1.1.4) If it is a pointer to a RECORD type, check if the RECORD type
* is interesting. If it is not interesting, then this struct
* type will also be deemed as not interesting. */
if (!strcmp(member_typename, type_name) ||
!rai_interesting_struct_type(member_type))
return false;
/* (1.2) Check if the member is an instance of another non-interesting
* struct type. If so, return false. */
} else if (TREE_CODE(member_tree) == RECORD_TYPE &&
!rai_interesting_struct_type(member_tree)) {
return false;
}
}
/* If we are unable to iterate through the fields of the RECORD_TYPE
* deem the struct type to be non-interesting. */
if (!has_fields)
return false;
return true;
}
/*
* rai_hardcoded_blacklist_typename - check if type_name is a blacklisted type.
*
* @type_name: string representation of a type.
*
* Returns:
* true: if type_name is present in blacklisted_typenames.
* false: otherwise.
*/
static bool rai_hardcoded_blacklist_typename(const char *type_name)
{
const char **tmp;
if (!type_name)
return false;
for (tmp = blacklisted_typenames; *tmp; tmp++)
if (!strcmp(*tmp, type_name))
return true;
return false;
}
static bool rai_interesting_struct_type(tree type_tree)
{
const char *type_name;
int ret;
if (!IS_RECORD_TYPE(type_tree))
return false;
type_name = rai_structtype_str(type_tree);
if (!type_name)
return false;
/* If type_name is already categorized as interesting, return true */
if (cache_contains(&interesting, type_name) == CACHE_CONTAINS_SUCCESS)
return true;
/* If type_name is already checked, return. Else insert and continue */
ret = cache_notcontains_insert(&checked, type_name);
if (ret == CACHE_CONTAINS_SUCCESS)
return false;
if (ret == CACHE_INSERTION_FAILED)
die("cache_notcontains_insert() returned %d at %d", ret, __LINE__);
if (rai_hardcoded_blacklist_typename(type_name))
return false;
if (rai_check_interesting_sttype(type_tree, type_name)) {
ret = cache_notcontains_insert(&interesting, type_name);
if (ret == CACHE_INSERTION_FAILED)
die("cache_notcontains_insert() returned %d at %d\n", ret, __LINE__);
return true;
} else {
return false;
}
return true;
}
/*
* rai_stage1_execute - Entry point of plugin stage 1.
*
* In stage 1, the plugin iterates over each global variable in a translation
* unit and determines if its type is interesting.
*/
static void rai_stage1_execute(void)
{
varpool_node_ptr node;
FOR_EACH_VARIABLE(node) {
tree var_node = NODE_DECL(node);
gcc_assert(TREE_CODE(var_node) == VAR_DECL);
tree type_tree;
type_tree = TREE_TYPE(var_node);
rai_interesting_struct_type(type_tree);
}
}
/*
* is_global - Returns true if var_tree corresponds to a global variable.
*/
static bool is_global(tree var_tree)
{
varpool_node_ptr node;
FOR_EACH_VARIABLE(node) {
tree var_node = NODE_DECL(node);
gcc_assert(TREE_CODE(var_node) == VAR_DECL);
if (var_node == var_tree) return true;
}
return false;
}
/*
* is_annotated - Returns true of the declaration is annotated.
*
* @decl: tree node representing variable or function.
* @sname: annotation to check on decl
*/
static bool is_annotated(tree decl, const char *sname)
{
tree section, attr_value;
section = lookup_attribute("section", DECL_ATTRIBUTES(decl));
if (!section || !TREE_VALUE(section))
return false;
FOR_EACH_ATTR_VALUE(section, attr_value) {
const char *str = TREE_STRING_POINTER(TREE_VALUE(attr_value));
if (!strncmp(str, sname, strlen(sname)))
return true;
}
return false;
}
/*
* is_fn_annotated: Returns true if a function is annotated with __init.
*
* @var_decl: tree node representing a variable.
*/
static bool is_fn_annotated(tree var_decl)
{
return is_annotated(var_decl, __init);
}
/*
* is_var_annotated: Returns true if a variable declaration is annotated with
* either __initdata or __ro_after_init.
*
* @var_decl: tree node representing a function.
*/
static bool is_var_annotated(tree var_decl)
{
return is_annotated(var_decl, __initdata) ||
is_annotated(var_decl, __ro_after_init);
}
/*
* rai_check_assign_stmt: Investigate an assignment statement.
*
* Given a gimple statement, write a log entry to STAGE2_FINAL iff the
* following conditions are met.
* - The write is to an instance of an interesting struct type.
* - The write is to a global variable that is not already annotated.
*
* A single line of the log entry will have the following information.
* - name of struct instance whose member is being written to.
* - name of struct type corresponding to the instance.
* - function name within which the write occurs.
* - whether or not the function is annotated with __init.
*
* int a;
* struct sometype {
* int b;
* };
* struct sometype st;
*
* void func1(void) {
* a = 10;
* st.b = 123;
* }
*
* void __init func2(void) {
* st.b = 234;
* };
*
* In the above example, `struct sometype` is interesting. The following log
* records(without quotes) will be written to STAGE2_FINAL.
* "v:st t:sometype fn:func1 status:NK"
* "v:st t:sometype fn:func2 status:OK"
*
* The status fields correspond to whether or not they were written to from a
* function annotated with __init.
*
* @stmt: statement to check for a write.
*/
static void rai_check_assign_stmt(gimple stmt)
{
tree lhs, arg0, arg0_type;
char buffer[LINELEN];
int ret;
lhs = gimple_assign_lhs(stmt);
if (TREE_CODE(lhs) != COMPONENT_REF)
return;
arg0 = TREE_OPERAND(lhs, 0);
if (TREE_CODE(arg0) != VAR_DECL)
return;
if (!is_global(arg0) || is_var_annotated(arg0))
return;
arg0_type = TREE_TYPE(arg0);
if (TREE_CODE(arg0_type) != RECORD_TYPE)
return;
if (cache_contains(&interesting, rai_structtype_str(arg0_type)) == CACHE_CONTAINS_FAILED)
return;
memset(buffer, 0, LINELEN);
snprintf(buffer, LINELEN-1, "v:%s t:%s fn:%s status:%s",
IDENTIFIER_POINTER(DECL_NAME(arg0)),
rai_structtype_str(arg0_type),
DECL_NAME_POINTER(current_function_decl),
is_fn_annotated(current_function_decl) ? "OK" : "NK");
if ((ret = cache_notcontains_insert(&results, buffer)) == CACHE_INSERTION_FAILED) {
die("cache_notcontains_insert() returned %d at %d\n", ret, __LINE__);
exit(-1);
}
}
/*
* rai_stage2_execute: Entry point plugin stage 2.
*
* In stage 2 the plugin iterates over each statement of each basic block
* of each function in a translation unit, and processes GIMPLE_ASSIGN
* statements.
*/
static void rai_stage2_execute(void)
{
basic_block bb;
FOR_ALL_BB_FN(bb, cfun) {
gimple_stmt_iterator gsi;
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
gimple stmt = gsi_stmt(gsi);
if (gimple_code(stmt) == GIMPLE_ASSIGN)
rai_check_assign_stmt(stmt);
}
}
}
/* Callback definitions */
static unsigned int roafteriniter_execute(void)
{
if (!stage2)
rai_stage1_execute();
else
rai_stage2_execute();
return 0;
}
/*
* rai_callback_finish: Unmap the caches from memory.
*/
static void rai_callback_finish(void *event_data __unused,
void *user_data __unused)
{
cache_unmap(&interesting);
if (!stage2)
cache_unmap(&checked);
if (stage2)
cache_unmap(&results);
}