1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
import gcc
from gccutils import get_src_for_loc, pformat, cfg_to_dot, invoke_dot
# Need to add `-fplugin=python.so -fplugin-arg-python-script=tools/check-throw-without-free.py` to CFLAGS in the Makefile
# We'll implement this as a custom pass, to be called directly after the
# builtin 'cfg' pass, which generates the CFG:
def collect_control_flows(bb, path, seen):
if bb.index in seen:
seen[bb.index] = 2
else:
seen[bb.index] = 1
paths = []
new_path = [bb]
new_path.extend(path)
if not bb.preds:
paths.append(new_path)
for edge in bb.preds:
pred = edge.src
if pred.index in seen and seen[pred.index] > 1:
continue
paths.extend(collect_control_flows(pred, new_path, seen))
return paths
#alloc_funcs = {'MVM_string_utf8_c8_encode_C_string'} # currently adding this causes a segfault in gcc when compiling src/io/filewatchers.c
alloc_funcs = {'MVM_malloc', 'MVM_calloc', 'MVM_fixed_size_allocate', 'MVM_fixed_size_allocate_zeroed', 'ANSIToUTF8', 'MVM_bytecode_dump', 'MVM_exception_backtrace_line', 'MVM_nativecall_unmarshal_string', 'MVM_serialization_read_cstr', 'MVM_spesh_dump', 'MVM_spesh_dump_arg_guard', 'MVM_spesh_dump_planned', 'MVM_spesh_dump_stats', 'MVM_staticframe_file_location', 'MVM_string_ascii_encode', 'MVM_string_ascii_encode_any', 'MVM_string_ascii_encode_substr', 'MVM_string_encode', 'MVM_string_gb18030_encode_substr', 'MVM_string_gb2312_encode_substr', 'MVM_string_latin1_encode', 'MVM_string_latin1_encode_substr', 'MVM_string_shiftjis_encode_substr', 'MVM_string_utf16_encode', 'MVM_string_utf16_encode_substr', 'MVM_string_utf16_encode_substr_main', 'MVM_string_utf16be_encode_substr', 'MVM_string_utf16le_encode_substr', 'MVM_string_utf8_c8_encode', 'MVM_string_utf8_c8_encode_substr', 'MVM_string_utf8_encode', 'MVM_string_utf8_encode_C_string', 'MVM_string_utf8_encode_substr', 'MVM_string_utf8_maybe_encode_C_string', 'MVM_string_windows1251_encode_substr', 'MVM_string_windows1252_encode_substr', 'MVM_string_windows125X_encode_substr', 'NFG_check_make_debug_string', 'NFG_checker', 'UnicodeToUTF8', 'UnicodeToUTF8', 'base64_encode', 'callback_handler', 'get_signature_char', 'twoway_memmem_uint32', 'u64toa_naive_worker'}
free_funcs = {'MVM_free', 'MVM_free_null', 'MVM_fixed_size_free', 'MVM_fixed_size_free_at_safepoint', 'free_repr_data', 'cleanup_all', 'MVM_tc_destroy'}
def check_code_for_throw_without_free(fun):
for bb in fun.cfg.basic_blocks:
for ins in bb.gimple:
if isinstance(ins, gcc.GimpleCall) and isinstance(ins.fn, gcc.AddrExpr) and ins.fn.operand.name == 'MVM_exception_throw_adhoc':
cfs = collect_control_flows(bb, [], {})
for cf in cfs:
allocs = set()
for bb in cf:
for ins in bb.gimple:
if isinstance(ins, gcc.GimpleCall):
if isinstance(ins.fn, gcc.AddrExpr): # plain function call is AddrExpr, other things could be function pointers
if ins.fn.operand.name in alloc_funcs:
allocs.add(ins.lhs)
if ins.fn.operand.name in free_funcs:
if ins.args[0] in allocs: # there can be false positives because the lhs of the alloc and the argument to the free can get different temp names from gcc
allocs.remove(ins.args[0])
if ins.fn.operand.name == 'MVM_exception_throw_adhoc':
if allocs:
print('\nPossible missing free before a throw in ' + str(fun.decl.name) + ' at ' + str(cf[-2].gimple[-1].loc) + ', things that might need to be freed: ' + str(allocs) + '\n')
class CheckThrowWithoutFree(gcc.GimplePass):
def execute(self, fun):
# (the CFG should be set up by this point, and the GIMPLE is not yet
# in SSA form)
if fun and fun.cfg:
check_code_for_throw_without_free(fun)
ps = CheckThrowWithoutFree(name='check-throw-without-free')
ps.register_after('cfg')
|