mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-26 06:10:56 +00:00
[qfcc] Use flow analysis for dealloc check
I decided that the check for whether control reaches the end of the function without performing some necessary action (eg, invoking [super dealoc] in a derived -dealoc) is conceptually the return statement using a pseudo operand and the necessary action defining that pseudo operand and thus is the same as checking for uninitialised variables. Thus, add a pseudo operand type and use one to represent the invocation of [super alloc], with a special function to call when the "used" pseudo operand is "uninitialised". While I currently don't know what else pseudo operands could be used for, the system should be flexible enough to add any check. Fixes #24
This commit is contained in:
parent
f903211362
commit
22c67fc268
5 changed files with 173 additions and 18 deletions
|
@ -97,6 +97,7 @@ typedef struct function_s {
|
|||
struct statement_s **statements;
|
||||
int num_statements;
|
||||
int pseudo_addr;///< pseudo address space for flow analysis
|
||||
struct pseudoop_s *pseudo_ops;///< pseudo operands used by this function
|
||||
} function_t;
|
||||
|
||||
extern function_t *current_func;
|
||||
|
|
|
@ -39,8 +39,11 @@ typedef enum {
|
|||
op_temp,
|
||||
op_alias,
|
||||
op_nil,
|
||||
op_pseudo,
|
||||
} op_type_e;
|
||||
|
||||
struct expr_s;
|
||||
|
||||
typedef struct tempop_s {
|
||||
struct def_s *def;
|
||||
int offset;
|
||||
|
@ -53,6 +56,13 @@ typedef struct tempop_s {
|
|||
int flowaddr; ///< "address" of temp in flow analysis, != 0
|
||||
} tempop_t;
|
||||
|
||||
typedef struct pseudoop_s {
|
||||
struct pseudoop_s *next;
|
||||
const char *name;
|
||||
struct flowvar_s *flowvar;
|
||||
void (*uninitialized) (struct expr_s *expr, struct pseudoop_s *op);
|
||||
} pseudoop_t;
|
||||
|
||||
typedef struct operand_s {
|
||||
struct operand_s *next;
|
||||
op_type_e op_type;
|
||||
|
@ -66,6 +76,7 @@ typedef struct operand_s {
|
|||
struct ex_label_s *label;
|
||||
tempop_t tempop;
|
||||
struct operand_s *alias;
|
||||
pseudoop_t *pseudoop;
|
||||
};
|
||||
} operand_t;
|
||||
|
||||
|
@ -101,6 +112,9 @@ typedef struct statement_s {
|
|||
operand_t *opc;
|
||||
struct expr_s *expr; ///< source expression for this statement
|
||||
int number; ///< number of this statement in function
|
||||
operand_t *use; ///< list of pseudo operands used
|
||||
operand_t *def; ///< list of pseudo operands defined
|
||||
operand_t *kill; ///< list of pseudo operands killed
|
||||
} statement_t;
|
||||
|
||||
typedef struct sblock_s {
|
||||
|
|
|
@ -121,6 +121,8 @@ get_operand_def (expr_t *expr, operand_t *op)
|
|||
return get_operand_def (expr, op->alias);
|
||||
case op_nil:
|
||||
internal_error (expr, "unexpected nil operand");
|
||||
case op_pseudo:
|
||||
internal_error (expr, "unexpected pseudo operand");
|
||||
}
|
||||
internal_error (expr, "unexpected operand");
|
||||
return 0;
|
||||
|
|
|
@ -283,6 +283,8 @@ flowvar_get_def (flowvar_t *var)
|
|||
internal_error (op->expr, "unexpected alias operand");
|
||||
case op_nil:
|
||||
internal_error (op->expr, "unexpected nil operand");
|
||||
case op_pseudo:
|
||||
internal_error (op->expr, "unexpected pseudo operand");
|
||||
}
|
||||
internal_error (op->expr, "oops, blue pill");
|
||||
return 0;
|
||||
|
@ -311,6 +313,11 @@ flow_get_var (operand_t *op)
|
|||
op->def->flowvar = new_flowvar ();
|
||||
return op->def->flowvar;
|
||||
}
|
||||
if (op->op_type == op_pseudo) {
|
||||
if (!op->pseudoop->flowvar)
|
||||
op->pseudoop->flowvar = new_flowvar ();
|
||||
return op->pseudoop->flowvar;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -349,6 +356,27 @@ count_operand (operand_t *op)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
count_operand_chain (operand_t *op)
|
||||
{
|
||||
int count = 0;
|
||||
while (op) {
|
||||
count += count_operand (op);
|
||||
op = op->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Allocate flow analysis pseudo address space.
|
||||
*/
|
||||
static int
|
||||
get_pseudo_address (function_t *func, int size)
|
||||
{
|
||||
int addr = func->pseudo_addr;
|
||||
func->pseudo_addr += size;
|
||||
return addr;
|
||||
}
|
||||
|
||||
/** Allocate flow analysis pseudo address space to a temporary variable.
|
||||
*
|
||||
* If the operand already has an address allocated (flowvar_t::flowaddr is
|
||||
|
@ -376,8 +404,7 @@ get_temp_address (function_t *func, operand_t *op)
|
|||
top = top->tempop.alias;
|
||||
}
|
||||
if (!top->tempop.flowaddr) {
|
||||
top->tempop.flowaddr = func->pseudo_addr;
|
||||
func->pseudo_addr += top->size;
|
||||
top->tempop.flowaddr = get_pseudo_address (func, top->size);
|
||||
}
|
||||
if (top->tempop.offset) {
|
||||
internal_error (top->expr, "real tempop with a non-zero offset");
|
||||
|
@ -412,7 +439,9 @@ add_operand (function_t *func, operand_t *op)
|
|||
var->number = func->num_vars++;
|
||||
var->op = op;
|
||||
func->vars[var->number] = var;
|
||||
if (op->op_type == op_temp) {
|
||||
if (op->op_type == op_pseudo) {
|
||||
var->flowaddr = get_pseudo_address (func, 1);
|
||||
} else if (op->op_type == op_temp) {
|
||||
var->flowaddr = get_temp_address (func, op);
|
||||
} else if (flowvar_is_local (var)) {
|
||||
var->flowaddr = func->num_statements + def_offset (var->op->def);
|
||||
|
@ -420,6 +449,15 @@ add_operand (function_t *func, operand_t *op)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_operand_chain (function_t *func, operand_t *op)
|
||||
{
|
||||
while (op) {
|
||||
add_operand (func, op);
|
||||
op = op->next;
|
||||
}
|
||||
}
|
||||
|
||||
/** Create symbols and defs for params/return if not already available.
|
||||
*/
|
||||
static symbol_t *
|
||||
|
@ -528,7 +566,7 @@ flow_build_vars (function_t *func)
|
|||
|
||||
// First, run through the statements making sure any accessed variables
|
||||
// have their flowvars reset. Local variables will be fine, but global
|
||||
// variables make have had flowvars added in a previous function, and it's
|
||||
// variables may have had flowvars added in a previous function, and it's
|
||||
// easier to just clear them all.
|
||||
// This is done before .return and .param so they won't get reset just
|
||||
// after being counted
|
||||
|
@ -556,6 +594,10 @@ flow_build_vars (function_t *func)
|
|||
flow_analyze_statement (s, 0, 0, 0, operands);
|
||||
for (j = 0; j < 4; j++)
|
||||
num_vars += count_operand (operands[j]);
|
||||
// count any pseudo operands referenced by the statement
|
||||
num_vars += count_operand_chain (s->use);
|
||||
num_vars += count_operand_chain (s->def);
|
||||
num_vars += count_operand_chain (s->kill);
|
||||
}
|
||||
if (!num_vars)
|
||||
return;
|
||||
|
@ -580,6 +622,9 @@ flow_build_vars (function_t *func)
|
|||
flow_analyze_statement (s, 0, 0, 0, operands);
|
||||
for (j = 0; j < 4; j++)
|
||||
add_operand (func, operands[j]);
|
||||
add_operand_chain (func, s->use);
|
||||
add_operand_chain (func, s->def);
|
||||
add_operand_chain (func, s->kill);
|
||||
}
|
||||
// and set the use/def sets for the vars (has to be a separate pass
|
||||
// because the allias handling reqruires the flow address to be valid
|
||||
|
@ -892,15 +937,24 @@ flow_uninit_scan_statements (flownode_t *node, set_t *defs, set_t *uninit)
|
|||
for (var_i = set_first (stuse); var_i; var_i = set_next (var_i)) {
|
||||
var = node->graph->func->vars[var_i->element];
|
||||
if (set_is_intersecting (defs, var->define)) {
|
||||
def_t *def = flowvar_get_def (var);
|
||||
if (def) {
|
||||
if (options.warnings.uninited_variable) {
|
||||
warning (st->expr, "%s may be used uninitialized",
|
||||
def->name);
|
||||
if (var->op->op_type == op_pseudo) {
|
||||
pseudoop_t *op = var->op->pseudoop;
|
||||
if (op->uninitialized) {
|
||||
op->uninitialized (st->expr, op);
|
||||
} else {
|
||||
internal_error (0, "pseudoop uninitialized not set");
|
||||
}
|
||||
} else {
|
||||
bug (st->expr, "st %d, uninitialized temp %s",
|
||||
st->number, operand_string (var->op));
|
||||
def_t *def = flowvar_get_def (var);
|
||||
if (def) {
|
||||
if (options.warnings.uninited_variable) {
|
||||
warning (st->expr, "%s may be used uninitialized",
|
||||
def->name);
|
||||
}
|
||||
} else {
|
||||
bug (st->expr, "st %d, uninitialized temp %s",
|
||||
st->number, operand_string (var->op));
|
||||
}
|
||||
}
|
||||
}
|
||||
// avoid repeat warnings in this node
|
||||
|
@ -1063,7 +1117,7 @@ flow_add_op_var (set_t *set, operand_t *op, int is_use)
|
|||
// defs properly, but I am as yet uncertain of how.
|
||||
if (op->op_type == op_temp) {
|
||||
tempop_visit_all (&op->tempop, ol, flow_tempop_add_aliases, set);
|
||||
} else {
|
||||
} else if (op->op_type == op_def) {
|
||||
def_visit_all (op->def, ol, flow_def_add_aliases, set);
|
||||
}
|
||||
}
|
||||
|
@ -1099,12 +1153,24 @@ flow_analyze_statement (statement_t *s, set_t *use, set_t *def, set_t *kill,
|
|||
operand_t *aux_op1 = 0;
|
||||
operand_t *aux_op2 = 0;
|
||||
|
||||
if (use)
|
||||
if (use) {
|
||||
set_empty (use);
|
||||
if (def)
|
||||
for (operand_t *op = s->use; op; op = op->next) {
|
||||
flow_add_op_var (use, op, 1);
|
||||
}
|
||||
}
|
||||
if (def) {
|
||||
set_empty (def);
|
||||
if (kill)
|
||||
for (operand_t *op = s->def; op; op = op->next) {
|
||||
flow_add_op_var (def, op, 0);
|
||||
}
|
||||
}
|
||||
if (kill) {
|
||||
set_empty (kill);
|
||||
for (operand_t *op = s->kill; op; op = op->next) {
|
||||
flow_add_op_var (kill, op, 0);
|
||||
}
|
||||
}
|
||||
if (operands) {
|
||||
for (i = 0; i < FLOW_OPERANDS; i++)
|
||||
operands[i] = 0;
|
||||
|
|
|
@ -70,6 +70,7 @@ const char *op_type_names[] = {
|
|||
"op_temp",
|
||||
"op_alias",
|
||||
"op_nil",
|
||||
"op_pseudo",
|
||||
};
|
||||
|
||||
const char *st_type_names[] = {
|
||||
|
@ -183,7 +184,9 @@ operand_string (operand_t *op)
|
|||
buf);
|
||||
}
|
||||
case op_nil:
|
||||
return va (0, "nil");
|
||||
return "nil";
|
||||
case op_pseudo:
|
||||
return va (0, "pseudo: %s", op->pseudoop->name);
|
||||
}
|
||||
return ("??");
|
||||
}
|
||||
|
@ -260,6 +263,9 @@ _print_operand (operand_t *op)
|
|||
case op_nil:
|
||||
printf ("nil");
|
||||
break;
|
||||
case op_pseudo:
|
||||
printf ("pseudo: %s", op->pseudoop->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +276,20 @@ print_operand (operand_t *op)
|
|||
puts ("");
|
||||
}
|
||||
|
||||
static void
|
||||
print_operand_chain (const char *name, operand_t *op)
|
||||
{
|
||||
if (op) {
|
||||
printf (" %s:", name);
|
||||
while (op) {
|
||||
printf (" ");
|
||||
_print_operand (op);
|
||||
op = op->next;
|
||||
}
|
||||
printf ("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_statement (statement_t *s)
|
||||
{
|
||||
|
@ -283,8 +303,12 @@ print_statement (statement_t *s)
|
|||
if (s->opc)
|
||||
_print_operand (s->opc);
|
||||
printf (")\n");
|
||||
print_operand_chain ("use", s->use);
|
||||
print_operand_chain ("def", s->def);
|
||||
print_operand_chain ("kill", s->kill);
|
||||
}
|
||||
|
||||
static pseudoop_t *pseudoops_freelist;
|
||||
static sblock_t *sblocks_freelist;
|
||||
static statement_t *statements_freelist;
|
||||
static operand_t *operands_freelist;
|
||||
|
@ -319,6 +343,16 @@ new_statement (st_type_t type, const char *opcode, expr_t *expr)
|
|||
return statement;
|
||||
}
|
||||
|
||||
static pseudoop_t *
|
||||
new_pseudoop (const char *name)
|
||||
{
|
||||
pseudoop_t *pseudoop;
|
||||
ALLOC (256, pseudoop_t, pseudoops, pseudoop);
|
||||
pseudoop->name = save_string (name);
|
||||
return pseudoop;
|
||||
|
||||
}
|
||||
|
||||
static operand_t *
|
||||
new_operand (op_type_e op, expr_t *expr, void *return_addr)
|
||||
{
|
||||
|
@ -359,6 +393,16 @@ free_sblock (sblock_t *sblock)
|
|||
FREE (sblocks, sblock);
|
||||
}
|
||||
|
||||
static operand_t *
|
||||
pseudo_operand (pseudoop_t *pseudoop, expr_t *expr)
|
||||
{
|
||||
operand_t *op;
|
||||
op = new_operand (op_pseudo, expr, __builtin_return_address (0));
|
||||
op->pseudoop = pseudoop;
|
||||
op->size = 1;
|
||||
return op;
|
||||
}
|
||||
|
||||
operand_t *
|
||||
nil_operand (type_t *type, expr_t *expr)
|
||||
{
|
||||
|
@ -729,6 +773,7 @@ operand_address (operand_t *reference, expr_t *e)
|
|||
case op_value:
|
||||
case op_label:
|
||||
case op_nil:
|
||||
case op_pseudo:
|
||||
break;
|
||||
}
|
||||
internal_error (e, "invalid operand type for operand address: %s",
|
||||
|
@ -2029,11 +2074,31 @@ remove_dead_blocks (sblock_t *blocks)
|
|||
return did_anything;
|
||||
}
|
||||
|
||||
static void
|
||||
super_dealloc_warning (expr_t *expr, pseudoop_t *op)
|
||||
{
|
||||
warning (expr,
|
||||
"control may reach end of derived -dealloc without invoking %s",
|
||||
op->name);
|
||||
}
|
||||
|
||||
static void
|
||||
search_for_super_dealloc (sblock_t *sblock)
|
||||
{
|
||||
operand_t *op;
|
||||
pseudoop_t *super_dealloc = new_pseudoop ("[super dealloc]");
|
||||
int super_dealloc_found = 0;
|
||||
super_dealloc->next = current_func->pseudo_ops;
|
||||
current_func->pseudo_ops = super_dealloc;
|
||||
super_dealloc->uninitialized = super_dealloc_warning;
|
||||
while (sblock) {
|
||||
for (statement_t *st = sblock->statements; st; st = st->next) {
|
||||
if (statement_is_return (st)) {
|
||||
op = pseudo_operand (super_dealloc, st->expr);
|
||||
op->next = st->use;
|
||||
st->use = op;
|
||||
continue;
|
||||
}
|
||||
if (!statement_is_call (st)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -2056,13 +2121,20 @@ search_for_super_dealloc (sblock_t *sblock)
|
|||
if (arg && arg->next && is_selector (arg)) {
|
||||
selector_t *sel = get_selector (st->expr->e.expr.e2);
|
||||
if (sel && strcmp (sel->name, "dealloc") == 0) {
|
||||
return;
|
||||
op = pseudo_operand (super_dealloc, st->expr);
|
||||
op->next = st->use;
|
||||
st->def = op;
|
||||
super_dealloc_found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sblock = sblock->next;
|
||||
}
|
||||
warning (0, "Derived class -dealloc does not call [super dealloc]");
|
||||
// warn only when NOT optimizing because flow analysis will catch the
|
||||
// missed invokation
|
||||
if (!super_dealloc_found && !options.code.optimize) {
|
||||
warning (0, "Derived class -dealloc does not call [super dealloc]");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
Loading…
Reference in a new issue