1#!/usr/bin/env python
2
3import os
4import re
5import sys
6
7def SplitSections(buffer):
8    """Spin through the input buffer looking for section header lines.
9    When found, the name of the section is extracted.  The entire contents
10    of that section is added to a result hashmap with the section name
11    as the key"""
12
13    # Match lines like
14    #              |section_name:
15    # capturing section_name
16    headerPattern = re.compile(r'^\s+\|([a-z _]+)\:$', re.MULTILINE)
17
18    sections = {}
19    start = 0
20    anchor = -1
21    sectionName = ''
22
23    while True:
24        # Look for a section header
25        result = headerPattern.search(buffer, start)
26
27        # If there are no more, add a section from the last header to EOF
28        if result is None:
29            if anchor is not -1:
30                sections[sectionName] = buffer[anchor]
31            return sections
32
33        # Add the lines from the last header, to this one to the sections
34        # map indexed by the section name
35        if anchor is not -1:
36            sections[sectionName] = buffer[anchor:result.start()]
37
38        sectionName = result.group(1)
39        start = result.end()
40        anchor = start
41
42    return sections
43
44def FindMethods(section):
45    """Spin through the 'method code index' section and extract all
46    method signatures.  When found, they are added to a result list."""
47
48    # Match lines like:
49    #             |[abcd] com/example/app/Class.method:(args)return
50    # capturing the method signature
51    methodPattern = re.compile(r'^\s+\|\[\w{4}\] (.*)$', re.MULTILINE)
52
53    start = 0
54    methods = []
55
56    while True:
57        # Look for a method name
58        result = methodPattern.search(section, start)
59
60        if result is None:
61            return methods
62
63        # Add the captured signature to the method list
64        methods.append(result.group(1))
65        start = result.end()
66
67def CallsMethod(codes, method):
68    """Spin through all the input method signatures.  For each one, return
69    whether or not there is method invokation line in the codes section that
70    lists the method as the target."""
71
72    start = 0
73
74    while True:
75        # Find the next reference to the method signature
76        match = codes.find(method, start)
77
78        if match is -1:
79            break;
80
81        # Find the beginning of the line the method reference is on
82        startOfLine = codes.rfind("\n", 0, match) + 1
83
84        # If the word 'invoke' comes between the beginning of the line
85        # and the method reference, then it is a call to that method rather
86        # than the beginning of the code section for that method.
87        if codes.find("invoke", startOfLine, match) is not -1:
88            return True
89
90        start = match + len(method)
91
92    return False
93
94
95
96def main():
97    if len(sys.argv) is not 2 or not sys.argv[1].endswith(".jar"):
98        print "Usage:", sys.argv[0], "<filename.jar>"
99        sys.exit()
100
101    command = 'dx --dex --dump-width=1000 --dump-to=-"" "%s"' % sys.argv[1]
102
103    pipe = os.popen(command)
104
105    # Read the whole dump file into memory
106    data = pipe.read()
107    sections = SplitSections(data)
108
109    pipe.close()
110    del(data)
111
112    methods = FindMethods(sections['method code index'])
113    codes = sections['codes']
114    del(sections)
115
116    print "Dead Methods:"
117    count = 0
118
119    for method in methods:
120        if not CallsMethod(codes, method):
121            print "\t", method
122            count += 1
123
124    if count is 0:
125        print "\tNone"
126
127if __name__ == '__main__':
128    main()
129