#!/usr/bin/env python
import os
import re
import sys
def SplitSections(buffer):
"""Spin through the input buffer looking for section header lines.
When found, the name of the section is extracted. The entire contents
of that section is added to a result hashmap with the section name
as the key"""
# Match lines like
# |section_name:
# capturing section_name
headerPattern = re.compile(r'^\s+\|([a-z _]+)\:$', re.MULTILINE)
sections = {}
start = 0
anchor = -1
sectionName = ''
while True:
# Look for a section header
result = headerPattern.search(buffer, start)
# If there are no more, add a section from the last header to EOF
if result is None:
if anchor is not -1:
sections[sectionName] = buffer[anchor]
return sections
# Add the lines from the last header, to this one to the sections
# map indexed by the section name
if anchor is not -1:
sections[sectionName] = buffer[anchor:result.start()]
sectionName = result.group(1)
start = result.end()
anchor = start
return sections
def FindMethods(section):
"""Spin through the 'method code index' section and extract all
method signatures. When found, they are added to a result list."""
# Match lines like:
# |[abcd] com/example/app/Class.method:(args)return
# capturing the method signature
methodPattern = re.compile(r'^\s+\|\[\w{4}\] (.*)$', re.MULTILINE)
start = 0
methods = []
while True:
# Look for a method name
result = methodPattern.search(section, start)
if result is None:
return methods
# Add the captured signature to the method list
methods.append(result.group(1))
start = result.end()
def CallsMethod(codes, method):
"""Spin through all the input method signatures. For each one, return
whether or not there is method invokation line in the codes section that
lists the method as the target."""
start = 0
while True:
# Find the next reference to the method signature
match = codes.find(method, start)
if match is -1:
break;
# Find the beginning of the line the method reference is on
startOfLine = codes.rfind("\n", 0, match) + 1
# If the word 'invoke' comes between the beginning of the line
# and the method reference, then it is a call to that method rather
# than the beginning of the code section for that method.
if codes.find("invoke", startOfLine, match) is not -1:
return True
start = match + len(method)
return False
def main():
if len(sys.argv) is not 2 or not sys.argv[1].endswith(".jar"):
print "Usage:", sys.argv[0], "<filename.jar>"
sys.exit()
command = 'dx --dex --dump-width=1000 --dump-to=-"" "%s"' % sys.argv[1]
pipe = os.popen(command)
# Read the whole dump file into memory
data = pipe.read()
sections = SplitSections(data)
pipe.close()
del(data)
methods = FindMethods(sections['method code index'])
codes = sections['codes']
del(sections)
print "Dead Methods:"
count = 0
for method in methods:
if not CallsMethod(codes, method):
print "\t", method
count += 1
if count is 0:
print "\tNone"
if __name__ == '__main__':
main()