def make_complex_analysis(self): """ Construct a Analysis instance that uses all features """ a = Analysis( metadata=Metadata(generator=Generator(name='cpychecker', version='0.11'), sut=SourceRpm(name='python-ethtool', version='0.7', release='4.fc19', buildarch='x86_64'), file_=File(givenpath='foo.c', abspath='/home/david/coding/foo.c'), stats=Stats(wallclocktime=0.4)), results=[ Issue(cwe=681, testid='refcount-too-high', location=Location(file=File( givenpath='foo.c', abspath='/home/david/coding/foo.c'), function=Function('bar'), point=Point(10, 15)), message=Message(text='something bad involving pointers'), notes=Notes('here is some explanatory text'), trace=Trace([ State(location=Location(file=File('foo.c', None), function=Function('bar'), point=Point(7, 12)), notes=Notes('first we do this')), State(location=Location(file=File('foo.c', None), function=Function('bar'), point=Point(8, 10)), notes=Notes('then we do that')), State(location=Location(file=File('foo.c', None), function=Function('bar'), range_=Range( Point(10, 15), Point(10, 25))), notes=Notes('then it crashes here')) ]), severity='really bad', customfields=CustomFields(foo='bar')), ], customfields=CustomFields( gccinvocation='gcc -I/usr/include/python2.7 -c foo.c'), ) return a, a.results[0]
def parse_file(data_file_obj, findbugs_version=None, sut=None, file_=None, stats=None): """ :param data_file_obj: file object containing findbugs scan result in xml format, it can be generated using command: fb analyze -xml:withMessages [jar_file] :type data_file_obj: file object :param findbugs_version: version of findbugs :type findbugs_version: str :return: Analysis instance """ generator = Generator(name="findbugs", version=findbugs_version) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) def parse_BugInstance(bugInstance): message = Message(bugInstance.find("LongMessage").text) # findbugs has no column information sourceLine = bugInstance.find("SourceLine") point = Point(int(sourceLine.get("start")), 0) function = bugInstance.find("Method").find("Message").text tmpIndex = function.rfind("In method ") + len("In method ") - 1 function = Function(function[tmpIndex + 1:]) path = sourceLine.get("sourcepath") path = File(path, None) location = Location(path, function, point) if DEBUG: print(str(location) + " " + str(message)) return Issue(None, None, location, message, None, None) tree = ET.parse(data_file_obj) root = tree.getroot() for bugInstance in root.findall("BugInstance"): issue = parse_BugInstance(bugInstance) if issue: analysis.results.append(issue) else: sys.stderr.write("fail to pass bugInstance=[%s]\n" % str(bugInstance)) return analysis
def make_failed_analysis(self): a = Analysis( metadata=Metadata(generator=Generator(name='yet-another-checker'), sut=None, file_=None, stats=None), results=[ Failure(failureid='out-of-memory', location=Location( file=File('foo.c', None), function=Function('something_complicated'), point=Point(10, 15)), message=Message('out of memory'), customfields=CustomFields(stdout='sample stdout', stderr='sample stderr', returncode=-9)) # (killed) ]) return a, a.results[0]
def parse_splint_csv(path): """ Parse a .csv file written by splint's "-csv FILENAME" option. Generate a list of Result instances. """ generator = Generator(name='splint') metadata = Metadata(generator, None, None, None) analysis = Analysis(metadata, []) with open(path, 'r') as f: reader = csv.reader(f) for raw_row in reader: # Skip the initial title row if raw_row[0] == 'Warning': continue rowobj = parse_row(raw_row) analysis.results.append(rowobj.to_issue()) return analysis
class FindbugsTestCase(unittest.TestCase): filepath = 'tests/resources/libjdom1-java_1.1.3-1_all.deb' firehose_results = Analysis( metadata=Metadata( generator=Generator( name='findbugs' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.findbugs.run_command', return_value=('2.0.3', '', 0)) def test_version(self, mock): name, ver = version() self.assertEquals(name, 'findbugs') self.assertEquals(ver, '2.0.3') @mock.patch('debile.slave.runners.findbugs.run_command', return_value=('2.0.3', '', 1)) def test_version_without_findbugs(self, mock): self.assertRaises(Exception, version) def test_findbugs(self): findbugs_analysis = findbugs(self.filepath, self.firehose_results) content = findbugs_analysis[1] self.assertTrue("The following classes needed for analysis" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) @mock.patch('debile.slave.runners.findbugs.run_command', return_value=(0, 'error', -1)) def test_findbugs_with_exception(self, mock): self.assertRaises(Exception, findbugs, self.filepath, self.firehose_results)
def make_simple_analysis(self): """ Construct a minimal Analysis instance """ a = Analysis( metadata=Metadata(generator=Generator(name='cpychecker'), sut=None, file_=None, stats=None), results=[ Issue(cwe=None, testid=None, location=Location(file=File('foo.c', None), function=None, point=Point(10, 15)), message=Message(text='something bad involving pointers'), notes=None, trace=None) ]) return a, a.results[0]
def parse_file(data_file, sut=None, file_=None, stats=None): """ for each line, the regex for SPARECODE_WARNING is matched :param data_file: file object containing build log :type data_file: file :return: Analysis instance """ generator = Generator(name='frama-c') metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) for line in data_file.readlines(): match_warning = FRAMA_C_SPARECODE_PATTERN.match(line) if match_warning: issue = parse_warning(match_warning) analysis.results.append(issue) return analysis
def parse_file(data_file, gccversion=None, sut=None, file_=None, stats=None): """ looks for groups of lines that start with a line identifying a function name, followed by one or more lines with a warning or note :param data_file: file object containing build log :type data_file: file :param gccversion: version of GCC that generated this report :type gccversion: str :return: Analysis instance """ # has a value only when in a block of lines where the first line identifies # a function and is followed by 0 or more warning lines generator = Generator(name='gcc', version=gccversion) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) current_func_name = None for line in data_file.readlines(): match_func = FUNCTION_PATTERN.match(line) match_global = GLOBAL_PATTERN.match(line) # if we found a line that describes a function name if match_func: current_func_name = match_func.group('func') elif match_global: current_func_name = GLOBAL_FUNC_NAME # if we think the next line might describe a warning elif current_func_name is not None: issue = parse_warning(line, current_func_name) if issue: analysis.results.append(issue) else: # reset this when we run out of warnings associated with it current_func_name = None return analysis
def test_lintian_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='lintian'), sut=None, file_=None, stats=None), results=[]) return lintian(filepath, firehorse_results)
def parse_file(infile): """ Parser flawfinder output :infile: file-like object :returns: Firehose Analysis object, representing the final XML. Flawfinder can generate multiple cwes for a single issue. Firehose's models does not supports multiple CWEs. For now, when multiple CWEs ocurrs, we get only the first one. A issue was created to track this bug: https://github.com/fedora-static-analysis/firehose/issues/35 """ line = infile.readline() generator = Generator(name='flawfinder', version=get_flawfinder_version(line)) metadata = Metadata(generator, None, None, None) analysis = Analysis(metadata, []) # A regex for "filename:linenum:" ISSUE_LINE_PATTERN = r"(\S.*)\:([0-9]+)\:" # A regex for the reported severity, e.g. "[2]" ISSUE_SEVERITY_PATTERN = r"\[([0-9]+)\]" # A regex for the reported testid, e.g. "(buffer)" ISSUE_TESTID_PATTERN = r"\(([a-z]+)\)" WHITESPACE = "\s+" FIRST_LINE_PATTERN = (ISSUE_LINE_PATTERN + WHITESPACE + ISSUE_SEVERITY_PATTERN + WHITESPACE + ISSUE_TESTID_PATTERN) prog = re.compile(FIRST_LINE_PATTERN) while line: m = prog.match(line) if m: issue_path = m.group(1) issue_line = m.group(2) issue_severity = m.group(3) testid = m.group(4) location = Location(file=File(issue_path, None), function=None, point=Point(int(issue_line), 0)) message_line = infile.readline() issue_message = "" while not prog.search(message_line) and message_line != "\n": # Build up issue_message as one line, stripping out # extraneous whitespace. if issue_message: issue_message += " " + message_line.strip() else: issue_message = message_line.strip() message_line = infile.readline() line = message_line cwes = [int(cwe) for cwe in re.findall("CWE-([0-9]+)", issue_message)] if cwes: first_cwe = int(cwes[0]) else: first_cwe = None issue = Issue(first_cwe, testid, location, Message(text=issue_message), notes=None, trace=None, severity=issue_severity, customfields=None) analysis.results.append(issue) else: line = infile.readline() return analysis
def test_roodi_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='roodi'), sut=None, file_=None, stats=None), results=[]) return roodi("tests/resources/bundler_1.7.4-1.dsc", firehorse_results)
def parse_plist(pathOrFile, analyzerversion=None, sut=None, file_=None, stats=None): """ Given a .plist file emitted by clang-static-analyzer (e.g. via scan-build), parse it and return an Analysis instance """ plist = plistlib.readPlist(pathOrFile) # We now have the .plist file as a hierarchy of dicts, lists, etc # Handy debug dump: if 0: pprint(plist) # A list of filenames, apparently referenced by index within # diagnostics: files = plist['files'] generator = Generator(name='clang-analyzer', version=analyzerversion) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) if 'clang_version' in plist: generator.version = plist['clang_version'] for diagnostic in plist['diagnostics']: if 0: pprint(diagnostic) cwe = None customfields = CustomFields() for key in ['category', 'issue_context', 'issue_context_kind']: if key in diagnostic: customfields[key] = diagnostic[key] message = Message(text=diagnostic['description']) loc = diagnostic['location'] location = Location( file=File(givenpath=files[loc['file']], abspath=None), # FIXME: doesn't tell us function name # TODO: can we patch this upstream? function=None, point=Point(int(loc['line']), int(loc['col']))) notes = None trace = make_trace(files, diagnostic['path']) issue = Issue( cwe, # Use the 'type' field for the testid: diagnostic['type'], location, message, notes, trace, customfields=customfields) analysis.results.append(issue) return analysis
def test_pep8_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='pep8'), sut=None, file_=None, stats=None), results=[]) return pep8(filepath, firehorse_results)
class CppcheckTestCase(unittest.TestCase): file_path="tests/resources/libjsoncpp_0.6.0~rc2-3.1.dsc" firehose_results = Analysis( metadata=Metadata( generator=Generator( name='cppcheck' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('Cppcheck 1.69', '', 0)) def test_cppcheck_version(self, mock): name, ver = version() self.assertEquals(name, 'Cppcheck') self.assertEquals(ver, '1.69') @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('Cppcheck 1.69', '', 1)) def test_cppcheck_version_with_error(self, mock): self.assertRaises(Exception, version) def test_cppcheck(self): cpp_analysis = cppcheck(self.file_path, self.firehose_results) xml_content = cpp_analysis[1] tree = lxml.etree.fromstring(xml_content.encode('utf-16')) i = 0 paths = [] lines = [] severity = [] messages = [] testid = [] for result in tree.xpath("//results/error"): paths.append(result.attrib['file']) lines.append(result.attrib['line']) severity.append(result.attrib['severity']) messages.append(result.attrib['msg']) testid.append(result.attrib['id']) i += 1 # It think it is safe to say that this number won't be less than 4 self.assertTrue(i > 4) # Check that some values exist (the order might change) self.assertTrue("src/lib_json/json_value.cpp" in paths) self.assertTrue("style" in severity) self.assertTrue("704" in lines) self.assertTrue("toomanyconfigs" in testid) @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('', ' ', 0)) def test_cppcheck_with_withespace_in_stderr(self, mock): cpp_analysis = cppcheck(self.file_path, self.firehose_results) self.assertEquals(cpp_analysis[0], self.firehose_results) self.assertEquals(cpp_analysis[1], ' ') self.assertFalse(cpp_analysis[2]) self.assertIsNone(cpp_analysis[3]) self.assertIsNone(cpp_analysis[4]) def test_cppcheck_wrappers(self): cpp_analysis = cppcheck(self.file_path, self.firehose_results) issues = parse_cppcheck(cpp_analysis[1]) i = 0 for issue in issues: if issue.testid == "toomanyconfigs": found = issue i += 1 self.assertEquals(found.testid, "toomanyconfigs") self.assertEquals(found.location.file.givenpath, "src/lib_json/json_value.cpp") self.assertEquals(found.location.point.line, 0) self.assertEquals(found.location.point.column, 0) self.assertEquals(found.severity, "style") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue(i > 4)
class JSHintTestCase(unittest.TestCase): filepath = "tests/resources/libjs-term.js_0.0.4-1.dsc" firehose_results = Analysis( metadata=Metadata( generator=Generator( name='jshint' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.jshint.run_command', return_value=('output', 'jshint v2.8.0', 0)) def test_jshint_version(self, mock): name, ver = version() self.assertEquals(name, 'jshint') self.assertEquals(ver, 'v2.8.0') @mock.patch('debile.slave.runners.jshint.run_command', return_value=('output', 'jshint v2.8.0', 1)) def test_version_raise_exception(self, mock): self.assertRaises(Exception, version) def test_jshint(self): jshint_analysis = jshint(self.filepath, self.firehose_results) content = jshint_analysis[1] self.assertTrue("Bad line breaking" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) @mock.patch('debile.slave.runners.jshint.run_command', return_value=(None, 'jshint v2.8.0', 1)) def test_jshint_with_none_output(self, mock): jshint_analysis = jshint(self.filepath, self.firehose_results) self.assertEquals(jshint_analysis[0], self.firehose_results) self.assertIsNone(jshint_analysis[1]) self.assertTrue(jshint_analysis[2]) self.assertIsNone(jshint_analysis[3]) self.assertIsNone(jshint_analysis[4]) def test_jshint_wrappers(self): jshint_analysis = jshint(self.filepath, self.firehose_results) issues = parse_jshint(jshint_analysis[1].splitlines()) i = 0 found = None for issue in issues: if issue.location.file.givenpath == "test/index.js" and \ issue.location.point.line==13 and issue.location.point.column==19: found = issue i += 1 print found self.assertEquals(found.testid, "W014") self.assertEquals(found.location.file.givenpath, "test/index.js") self.assertEquals(found.location.point.line, 13) self.assertEquals(found.location.point.column, 19) self.assertEquals(found.severity, "warning") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue("Bad line breaking" in found.message.text) self.assertTrue(i > 75)