| #!/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() |