├── README.markdown └── objc_cover.py /README.markdown: -------------------------------------------------------------------------------- 1 | Say you have this Objective-C code: 2 | 3 | - (void)notUsed { 4 | return; 5 | } 6 | 7 | - (void)actuallyUsed { 8 | return; 9 | } 10 | 11 | // ... 12 | 13 | [self actuallyUsed]; 14 | 15 | Thanks to `/usr/bin/otool`, you can get clues about potentially unused methods, like this: 16 | 17 | $ python objc_cover.py /Users/nst/Desktop/iCalReport 18 | # the following methods may be unreferenced 19 | -[MyClass notUsed] 20 | 21 | The idea was [put first](http://lists.apple.com/archives/objc-language/2009/Oct/msg00085.html) by "Luke the Hiesterman" on the Apple Objective-C list. 22 | -------------------------------------------------------------------------------- /objc_cover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | __author__ = "Nicolas Seriot" 4 | __date__ = "2010-03-01" 5 | __license__ = "GPL" 6 | 7 | import os 8 | import re 9 | import shutil 10 | import sys 11 | import tempfile 12 | 13 | def verified_macho_path(args): 14 | if len(sys.argv) != 2: 15 | return None 16 | 17 | path = sys.argv[1] 18 | 19 | if not os.path.isfile(path): 20 | return None 21 | 22 | ## Apparently there is a bug in otool -- it doesn't seem to like executables 23 | ## with spaces in the names. If this is the case, make a copy and analyze that. 24 | if ' ' in os.path.basename(path): 25 | ## don't remove the spaces, that could lead to an empty string 26 | new_filename = path.replace(' ', '_') 27 | new_path = os.path.join(tempfile.mkdtemp(), new_filename) 28 | shutil.copy(path, new_path) 29 | path = new_path 30 | 31 | cmd = "/usr/bin/file -b %r" % path 32 | s = os.popen(cmd).read() 33 | 34 | if not s.startswith('Mach-O'): 35 | return None 36 | 37 | return path 38 | 39 | def signature_cmp(m1, m2): 40 | cls1 = m1[2:].split(' ')[0] 41 | cls2 = m2[2:].split(' ')[0] 42 | 43 | result = cmp(cls1, cls2) 44 | 45 | if result == 0: # same class 46 | if m1.startswith('+') and m2.startswith('-'): 47 | return -1 48 | elif m1.startswith('-') and m2.startswith('+'): 49 | return +1 50 | else: # same sign 51 | return cmp(m1, m2) 52 | 53 | return result 54 | 55 | def implemented_methods(path): 56 | """ 57 | returns {'sel1':[sig1, sig2], 'sel2':[sig3]} 58 | """ 59 | 60 | re_sig_sel_ios = re.compile("\s*imp 0x\w+ ([+|-]\[.+\s(.+)\])") 61 | re_sig_sel_mac = re.compile("\s*imp ([+|-]\[.+\s(.+)\])") 62 | 63 | impl = {} # sel -> clsmtd 64 | 65 | for line in os.popen("/usr/bin/otool -oV %s" % path).xreadlines(): 66 | results = re_sig_sel_ios.findall(line) 67 | if not results: 68 | results = re_sig_sel_mac.findall(line) 69 | #print results 70 | 71 | if not results: 72 | continue 73 | (sig, sel) = results[0] 74 | 75 | if sel in impl: 76 | impl[sel].append(sig) 77 | else: 78 | impl[sel] = [sig] 79 | 80 | return impl 81 | 82 | def referenced_selectors(path): 83 | 84 | re_sel = re.compile("__TEXT:__objc_methname:(.+)") 85 | 86 | refs = set() 87 | 88 | lines = os.popen("/usr/bin/otool -v -s __DATA __objc_selrefs %s" % path).readlines() # ios & mac 89 | 90 | for line in lines: 91 | results = re_sel.findall(line) 92 | if results: 93 | refs.add(results[0]) 94 | 95 | return refs 96 | 97 | def potentially_unreferenced_methods(): 98 | implemented = implemented_methods(path) 99 | 100 | if not implemented: 101 | print "# can't find implemented methods" 102 | sys.exit(1) 103 | 104 | referenced = referenced_selectors(path) 105 | 106 | l = [] 107 | 108 | #print "-- implemented:", len(implemented) 109 | #print "-- referenced:", len(referenced) 110 | 111 | for sel in implemented: 112 | if sel not in referenced: 113 | for method in implemented[sel]: 114 | l.append(method) 115 | 116 | l.sort(signature_cmp) 117 | 118 | return l 119 | 120 | if __name__ == "__main__": 121 | 122 | path = verified_macho_path(sys.argv) 123 | if not path: 124 | print "Usage: %s MACH_O_FILE" % sys.argv[0] 125 | sys.exit(1) 126 | 127 | methods = potentially_unreferenced_methods() 128 | 129 | print "# the following methods may be unreferenced" 130 | for m in methods: 131 | print m 132 | --------------------------------------------------------------------------------