def maybe_diagnostic(self, cur): syn_statement = full_text_for_cursor(cur) syn_statement = syn_statement.replace('\n', '') syn_statement = syn_statement.replace('@synthesize ', '') if ',' in syn_statement: syn_statement = syn_statement.split(',')[-1] syn_statement = syn_statement.strip() d = LintDiagnostic() d.line_number = cur.location.line d.filename = cur.location.file.name d.context = '@synthesize ' + syn_statement d.category = self.category if '=' in syn_statement: if not self.re_long_form.match(syn_statement): d.message = "synthesize statement doesn't match "\ "template '@synthesize fooBar = fooBar_'" return d else: if not self.re_short_form.match(syn_statement) and\ not self.re_macro_form.match(syn_statement): d.message = "synthesize statement doesn't match "\ "template '@synthesize fooBar'" return d return None
def maybe_diagnostic(self, cur): # depth -1 -- OBJC_INSTANCE_METHOD_DECL (method signature) # depth 0 -- COMPOUND_STATMENT (curly braces) # depth 1 -- actual method code def calculate_complexity(c, depth=-1): result = 0 if c.kind == ci.CursorKind.COMPOUND_STMT and depth > 0: result = 1 if (depth < 3) else 2 result += sum(calculate_complexity(child, depth + 1) for child in c.get_children()) return result complexity = calculate_complexity(cur) line_count = full_text_for_cursor(cur).count('\n') # TODO configurable thresholds if complexity > 15 or\ complexity > 3 and line_count > 50: d = LintDiagnostic() d.line_number = cur.location.line d.message = "method {0} looks complex ({1} lines, {2} complexity)"\ .format(cur.displayname, line_count, complexity) d.filename = cur.location.file.name d.context = cur.spelling d.category = self.category return d return None
def get_diagnostics(self): def filter_cursors(cur): if cur.kind == self.cursor_kind: result = [cur] else: result = [] for child in cur.get_children(): if child.location.file and not isabs(child.location.file.name): result += filter_cursors(child) return result curs = chain(*map(filter_cursors, self.cursors)) regex = re.compile(self.regex_string) diags = [] for c in curs: if not regex.search(c.displayname) and c.location.file: d = LintDiagnostic() d.line_number = c.location.line d.message = self.message.format(cur=c) d.filename = c.location.file.name d.context = c.displayname d.category = self.category diags.append(d) return diags
def maybe_diagnostic(self, cur): # depth -1 -- OBJC_INSTANCE_METHOD_DECL (method signature) # depth 0 -- COMPOUND_STATMENT (curly braces) # depth 1 -- actual method code def calculate_complexity(c, depth=-1): result = 0 if c.kind == ci.CursorKind.COMPOUND_STMT and depth > 0: result = 1 if (depth < 3) else 2 result += sum(calculate_complexity(child, depth + 1) for child in c.get_children()) return result complexity = calculate_complexity(cur) line_count = full_text_for_cursor(cur).count("\n") # TODO configurable thresholds if complexity > 15 or complexity > 3 and line_count > 50: d = LintDiagnostic() d.line_number = cur.location.line d.message = "method {0} looks complex ({1} lines, {2} complexity)".format( cur.displayname, line_count, complexity ) d.filename = cur.location.file.name d.context = cur.spelling d.category = self.category return d return None
def maybe_diagnostic(self, cur): compound = None for c in cur.get_children(): if c.kind == ci.CursorKind.COMPOUND_STMT: compound = c if not compound: return None children = list(compound.get_children()) if not children: d = LintDiagnostic() d.line_number = cur.location.line d.message = "method {0} has empty implementation"\ .format(cur.displayname) d.filename = cur.location.file.name d.context = cur.displayname d.category = self.category return d elif len(children) == 1: only_statement = children[0] if only_statement.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\ and only_statement.displayname == cur.displayname\ and '[super ' == full_text_for_cursor(only_statement)[0:7]: # TODO: verify that all parameters are passed through unchanged d = LintDiagnostic() d.line_number = cur.location.line d.message = "method {0} only calls super implementation"\ .format(cur.displayname) d.filename = cur.location.file.name d.context = cur.displayname d.category = self.category return d return None
def get_diagnostics(self): result = [] lines = enumerate(self.text.split('\n')) for i, l in lines: if len(l) > self.config.max_line_length(default=100): d = LintDiagnostic() d.line_number = i + 1 d.message = 'Line too long ({0})'.format(len(l)) d.filename = self.filename d.context = l.strip() d.category = self.category result.append(d) return result
def maybe_diagnostic(cur): class_name = cur.displayname instance_methods = [c for c in cur.get_children() if\ c.kind == cindex.CursorKind.OBJC_INSTANCE_METHOD_DECL] def is_init(c): return self.re_init_decl.search(c.displayname) def is_dealloc(c): return self.re_dealloc_decl.search(c.displayname) init_count = len([m for m in instance_methods if is_init(m)]) dealloc_present = any(map(is_dealloc, instance_methods)) these_should_be_inits = instance_methods[:init_count] d = LintDiagnostic() d.line_number = cur.location.line d.filename = cur.location.file.name d.context = '' d.category = self.category if not all(map(is_init, these_should_be_inits)): d.message = INIT_TEMPLATE.format(len(these_should_be_inits), class_name) return d if dealloc_present: this_should_be_dealloc = instance_methods[init_count] if not is_dealloc(this_should_be_dealloc): d.message = DEALLOC_TEMPLATE.format(class_name) return d return None
def maybe_diagnostic(self, cur): if cur.type.kind == ci.TypeKind.CONSTANTARRAY: d = LintDiagnostic() d.line_number = cur.location.line d.message = "ivar {0} is C array, do you really want to do this?"\ .format(cur.displayname) d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d return None
def maybe_diagnostic(cur): class_name = cur.displayname instance_methods = [c for c in cur.get_children() if c.kind == cindex.CursorKind.OBJC_INSTANCE_METHOD_DECL] def is_init(c): return self.re_init_decl.search(c.displayname) def is_dealloc(c): return self.re_dealloc_decl.search(c.displayname) init_count = len([m for m in instance_methods if is_init(m)]) dealloc_present = any(map(is_dealloc, instance_methods)) these_should_be_inits = instance_methods[:init_count] d = LintDiagnostic() d.line_number = cur.location.line d.filename = cur.location.file.name d.context = "" d.category = self.category if not all(map(is_init, these_should_be_inits)): d.message = INIT_TEMPLATE.format(len(these_should_be_inits), class_name) return d if dealloc_present: this_should_be_dealloc = instance_methods[init_count] if not is_dealloc(this_should_be_dealloc): d.message = DEALLOC_TEMPLATE.format(class_name) return d return None
def maybe_diagnostic(self, cur): first_line = full_text_for_cursor(cur).split('\n')[0] if first_line[0] == self.first_char and\ not self.regex.match(first_line): d = LintDiagnostic() d.line_number = cur.location.line d.message = self.message d.filename = cur.location.file.name d.context = first_line d.category = self.category return d return None
def maybe_diagnostic(self, cur): try: cur.get_children().next() return None except StopIteration: pass d = LintDiagnostic() d.line_number = cur.location.line d.message = "empty compound statement" d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d
def maybe_diagnostic(self, cur): first_line = full_text_for_cursor(cur).split('\n')[0] if first_line[0] == '-' and not re_method_decl.match(first_line): d = LintDiagnostic() d.line_number = cur.location.line d.message = "method declaration whitespace doesn't match "\ "template '- (Foo)barBaz:(Baz)baz'" d.filename = cur.location.file.name d.context = first_line d.category = self.category return d return None
def maybe_diagnostic(self, cur): ivar_name = cur.displayname if cur.parent.kind == ci.CursorKind.OBJC_INTERFACE_DECL and\ not self.re_ivar_name.match(ivar_name): d = LintDiagnostic() d.line_number = cur.location.line d.message = "ivar {0} is not named likeThis_"\ .format(cur.displayname) d.filename = cur.location.file.name d.context = cur.displayname d.category = self.category return d return None
def get_diagnostics(self): result = [] lines = enumerate(self.text.split('\n')) regex = re.compile(self.regex_string) for i, l in lines: if regex.search(l): d = LintDiagnostic() d.line_number = i d.message = self.message d.filename = self.filename d.context = l d.category = self.category result.append(d) return result
def maybe_diagnostic(self, cur): if not cur.location.file: return None children = list(cur.get_children()) if 2 != len(children): print 'WTF' lhs, rhs = children[0], children[1] start = lhs.extent.end.offset end = rhs.extent.start.offset if end-start <= 1: return None with open(cur.location.file.name) as fi: op_and_whitespace = fi.read()[start:end] if '\n' in op_and_whitespace: return None for i in range(len(op_and_whitespace)): left, right = op_and_whitespace[i], op_and_whitespace[-(i+1)] if left != ' ' and right != ' ': # no more whitespace at either side break elif left == ' ' and right == ' ': # whitespace is symmetric so far continue else: d = LintDiagnostic() d.line_number = cur.location.line d.message = "Whitespace around binary operator isn't symmetric" d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d return None
def maybe_diagnostic(self, cur): if not cur.location.file: return None children = list(cur.get_children()) if 2 != len(children): print 'WTF' lhs, rhs = children[0], children[1] start = lhs.extent.end.offset end = rhs.extent.start.offset if end - start <= 1: return None with open(cur.location.file.name) as fi: op_and_whitespace = fi.read()[start:end] if '\n' in op_and_whitespace: return None for i in range(len(op_and_whitespace)): left, right = op_and_whitespace[i], op_and_whitespace[-(i + 1)] if left != ' ' and right != ' ': # no more whitespace at either side break elif left == ' ' and right == ' ': # whitespace is symmetric so far continue else: d = LintDiagnostic() d.line_number = cur.location.line d.message = "Whitespace around binary operator isn't symmetric" d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d return None
def maybe_diagnostic(self, cur): def get_objc_superclass(c): for child in c.get_children(): if child.kind == ci.CursorKind.OBJC_SUPER_CLASS_REF: return child.get_definition() return None if cur.type.kind != ci.TypeKind.OBJCOBJECTPOINTER: return None class_cursor = cur.type.get_pointee().get_declaration() var_name = cur.displayname for regex, expected_class_name in self.rules: if not regex.search(var_name): continue # walk objc class hierarchy trying to find # ancestor with name |expected_class_name| while class_cursor: class_name = class_cursor.displayname if class_name == expected_class_name: return None class_cursor = get_objc_superclass(class_cursor) else: actual_class = cur.type.get_pointee().get_declaration() d = LintDiagnostic() d.line_number = cur.location.line d.message = self.message_template.format(cur.displayname, actual_class.displayname, expected_class_name) d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d return None
def maybe_diagnostic(self, cur): def get_objc_superclass(c): for child in c.get_children(): if child.kind == ci.CursorKind.OBJC_SUPER_CLASS_REF: return child.get_definition() return None if cur.type.kind != ci.TypeKind.OBJCOBJECTPOINTER: return None class_cursor = cur.type.get_pointee().get_declaration() var_name = cur.displayname for regex, expected_class_name in self.rules: if not regex.search(var_name): continue # walk objc class hierarchy trying to find # ancestor with name |expected_class_name| while class_cursor: class_name = class_cursor.displayname if class_name == expected_class_name: return None class_cursor = get_objc_superclass(class_cursor) else: actual_class = cur.type.get_pointee().get_declaration() d = LintDiagnostic() d.line_number = cur.location.line d.message = self.message_template.format( cur.displayname, actual_class.displayname, expected_class_name) d.filename = cur.location.file.name d.context = full_text_for_cursor(cur) d.category = self.category return d return None
def get_clang_analyzer_diagnostics(filename, clang_args): plist_filename = '.sa' + os.path.basename(filename) invocation = ['clang', '--analyze'] invocation += clang_args invocation += ['-o', plist_filename] invocation += [ '-Xclang', '-analyzer-checker=core,deadcode,unix,osx,experimental' ] if '-fobjc-arc' in clang_args: invocation += [ '-Xclang', '-analyzer-disable-checker=experimental.osx.cocoa.Dealloc' ] invocation.append(filename) p = sp.Popen(invocation, stdout=sp.PIPE, stderr=sp.STDOUT) p.communicate() try: with open(plist_filename) as fi: root = plist.readPlist(fi) except IOError: return [] os.remove(plist_filename) result = [] for diag_dict in root['diagnostics']: d = LintDiagnostic() d.message = diag_dict['description'] d.line_number = diag_dict['location']['line'] d.filename = root['files'][diag_dict['location']['file']] with open(d.filename) as fi: d.context = fi.readlines()[d.line_number - 1].rstrip() d.category = 'ClangSA' result.append(d) return result
def get_clang_analyzer_diagnostics(filename, clang_args): plist_filename = '.sa' + os.path.basename(filename) invocation = ['clang', '--analyze'] invocation += clang_args invocation += ['-o', plist_filename] invocation += ['-Xclang', '-analyzer-checker=core,deadcode,unix,osx,experimental'] if '-fobjc-arc' in clang_args: invocation += ['-Xclang', '-analyzer-disable-checker=experimental.osx.cocoa.Dealloc'] invocation.append(filename) p = sp.Popen(invocation, stdout=sp.PIPE, stderr=sp.STDOUT) p.communicate() try: with open(plist_filename) as fi: root = plist.readPlist(fi) except IOError: return [] os.remove(plist_filename) result = [] for diag_dict in root['diagnostics']: d = LintDiagnostic() d.message = diag_dict['description'] d.line_number = diag_dict['location']['line'] d.filename = root['files'][diag_dict['location']['file']] with open(d.filename) as fi: d.context = fi.readlines()[d.line_number - 1].rstrip() d.category = 'ClangSA' result.append(d) return result
def get_diagnostics(self): local_cursors = [c for c in self.cursors\ if c.location.file.name == self.filename] def is_local_category(cur): return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\ and cur.displayname == '' local_categories = [c for c in local_cursors if is_local_category(c)] class Extension(object): def __init__(self, classname, ivars): self.classname = classname self.ivars = ivars exts = [] for lc in local_categories: class_cursor = list(lc.get_children())[0] ivars = [i for i in lc.get_children()\ if i.kind == ci.CursorKind.OBJC_IVAR_DECL] exts.append(Extension(class_cursor.displayname, ivars)) result = [] for ext in exts: class_impl = [c for c in local_cursors\ if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\ c.displayname == ext.classname][0] ivar_usages = dict([(i.displayname, 0) for i in ext.ivars]) method_impls = [c for c in class_impl.get_children()\ if c.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL] def collect_ivar_usages(cur): ivar_set = set() def go(cur): if cur.kind == ci.CursorKind.MEMBER_REF_EXPR: ivar_set.add(cur.displayname) for child in cur.get_children(): go(child) go(cur) return ivar_set for mi in method_impls: ivar_usages_in_method = collect_ivar_usages(mi) for usage in ivar_usages_in_method: if usage in ivar_usages: ivar_usages[usage] += 1 for ivar in ext.ivars: if ivar_usages[ivar.displayname] == 0: d = LintDiagnostic() d.category = self.category d.filename = self.filename d.line = ivar.location.line d.context = full_text_for_cursor(ivar) d.message = 'unused ivar ' + ivar.displayname result.append(d) elif ivar_usages[ivar.displayname] == 1 and self.has_arc: # FIXME: false positive if ivar is used as a storage # to fix this, we need to check if read access is possible # before write access in this method d = LintDiagnostic() d.category = self.category d.filename = self.filename d.line = ivar.location.line d.context = full_text_for_cursor(ivar) d.message = 'ivar {0} is used in only one method'.format( ivar.displayname) result.append(d) return result
def get_diagnostics(self): local_cursors = [c for c in self.cursors\ if c.location.file.name == self.filename] def is_local_category(cur): return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\ and cur.displayname == '' local_categories = [c for c in local_cursors if is_local_category(c)] class Extension(object): def __init__(self, filename, methods): self.filename = filename self.methods = methods exts = [] for lc in local_categories: class_cursor = list(lc.get_children())[0] ivars = [i for i in lc.get_children()\ if i.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL] exts.append(Extension(class_cursor.displayname, ivars)) result = [] for ext in exts: class_impls = [c for c in local_cursors\ if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\ c.displayname == ext.filename] yet_unused_method_names = [i.displayname for i in ext.methods] def traverse(cur): if cur.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\ and cur.displayname in yet_unused_method_names: yet_unused_method_names.remove(cur.displayname) if cur.kind == ci.CursorKind.OBJC_SELECTOR_EXPR: # '@selector(foo)' -> 'foo' selector_name = full_text_for_cursor(cur)[10:-1] if selector_name in yet_unused_method_names: yet_unused_method_names.remove(selector_name) if not yet_unused_method_names: return for child in cur.get_children(): traverse(child) for class_impl in class_impls: traverse(class_impl) for method in ext.methods: if method.displayname in yet_unused_method_names: d = LintDiagnostic() d.category = self.category d.filename = self.filename d.line = method.location.line d.context = full_text_for_cursor(method) d.message = 'unused method ' + method.displayname result.append(d) return result