Checking Suspicious Functions
Michael Hudson
mwh21 at cam.ac.uk
Sun Feb 27 10:30:05 EST 2000
Moshe Zadka <moshez at math.huji.ac.il> writes:
> After Tim and mine discusion, I thought writing code for a change would be
> nice. Here's a rough draft of my "checking whether a function can return a
> value from one place and return None from another" code:
>
> --- suspect.py
> import dis # for HAVE_ARGUMENT, and opname
>
> # currently we're to dumb to understand this is valid:
> # if x:
> # return 1
> # else:
> # return 0
>
I had written some code for splitting Python bytecode into basic
blocks a couple weeks back, so I've used that to whip up an effort
that *is* clever enough to cope with the above.
I think it takes exceptions to confuse it - how do they fit into the
basic block view of the world?
Large chunks of the standard library seem to pass muster, which is
good...
Comments appreciated (even if it's "Don't attach the file like *that*,
doofus").
Cheers,
M.
--
very few people approach me in real life and insist on proving they are
drooling idiots. -- Erik Naggum, comp.lang.lisp
===File ~/src/python/returnnanny/returnnanny.py=============
from bytecodehacks.code_editor import Function
from bytecodehacks.ops import *
import block_analysis
def _is_argumented_return(op):
if op.__class__ is not LOAD_CONST:
return 1
if op.arg is not 0:
return 1
return 0
def is_sensible(func):
code = Function(func).func_code
blocks = block_analysis.block_analyse(code)
returns = []
cmpval = 0
for block in blocks.values():
if block.body[-1].__class__ is RETURN_VALUE:
if len(block.body) < 2:
cmpval = 1
else:
returns.append( block.body[-2] )
if cmpval == 0:
cmpval = _is_argumented_return(returns[0])
for op in returns[1:]:
if _is_argumented_return(op) <> cmpval:
return 0
return 1
import types
def check_module(module):
for i in module.__dict__.values():
if type(i) is types.FunctionType:
print i.func_name, is_sensible(i)
============================================================
===File ~/src/python/returnnanny/block_analysis.py==========
from bytecodehacks.ops import *
class BasicBlock:
def __init__(self,body,label):
self.body = body
self.label = label
self.exits = []
self.entries = []
def __repr__(self):
return """\
%3d body: %s
exits %-20s entries %s"""%(self.label,`self.body`,`self.exits`,`self.entries`)
def isJump(op):
""" isJump(op: Opcode) -> Boolean
Return true if execution of op can result in transfer of control."""
return op.is_jump() or op.__class__ is RETURN_VALUE
def basic_blockize(code):
""" basic_blockize(code: CodeObject) -> {Int:BasicBlock}
Return the list of basic blocks of the code object. NB: |code| is
DESTRUCTIVELY MODIFIED to remove line nos. and SETUP_LOOPs. """
cs = code.co_code
targets = []
for label in cs.labels:
targets.append( label.op )
blocks = []
curblock = []
for op in cs:
if op in targets:
blocks.append(curblock)
curblock = []
curblock.append(op)
if isJump(op):
blocks.append(curblock)
curblock = []
blocks = filter(None,blocks)
block_objects = {}
for i in range(len(blocks)):
block_objects[i] = BasicBlock(blocks[i],i)
for block in block_objects.values():
last_op = block.body[-1]
if last_op.is_jump():
target = last_op.label.op
for t in block_objects.values():
if t.body[0] == target:
block.exits.append( t.label )
t.entries.append( block.label )
if (last_op.__class__ is not RETURN_VALUE
and last_op.__class__ is not JUMP_ABSOLUTE
and last_op.__class__ is not JUMP_FORWARD):
block.exits.append( block.label + 1 )
block_objects[ block.label + 1].entries.append( block.label )
return block_objects
def eliminate_unreachable_blocks(blocks):
""" eliminate_unreachable_blocks(blocks: {Int:BasicBlock}) -> None
remove unreachable blocks from the sequence """
reachable = [0]
for i in reachable:
for l in blocks[i].exits:
if l not in reachable:
reachable.append( l )
newblocks = []
for block in blocks.values():
if block.label in reachable:
newexits = []
for l in block.exits:
if l in reachable:
newexits.append( l )
block.exits = newexits
newentries = []
for l in block.entries:
if l in reachable:
newentries.append( l )
block.entries = newentries
newblocks.append( block )
else:
del blocks[block.label]
def print_blocks(blocks):
v = blocks.keys()
v.sort()
for i in v: print blocks[i]
def block_analyse(code):
""" block_analyse(code:CodeObject) -> {Int:BasicBlock}
1) Split code into basic blocks
2) Remove unreachable basic blocks. """
blocks = basic_blockize(code)
eliminate_unreachable_blocks(blocks)
return blocks
============================================================
More information about the Python-list
mailing list