diff --git a/examples/example-2.xml b/examples/example-2.xml index 64de821..dbb0a74 100644 --- a/examples/example-2.xml +++ b/examples/example-2.xml @@ -28,7 +28,7 @@ - + diff --git a/firehose.rng b/firehose.rng index c4301b0..90b188e 100644 --- a/firehose.rng +++ b/firehose.rng @@ -121,7 +121,7 @@ http://cwe.mitre.org/data/definitions/131.html --> - + diff --git a/firehose/model.py b/firehose/model.py index c74670d..f532339 100644 --- a/firehose/model.py +++ b/firehose/model.py @@ -68,6 +68,8 @@ def from_json(self, jsonobj): return jsonobj if self.type == float: return jsonobj + if self.type == list: + return jsonobj return self.resolve_type().from_json(jsonobj) def to_json(obj): @@ -233,7 +235,7 @@ def from_json(cls, jsonobj): raise TypeError('unknown type: %r' % jsonobj['type']) class Issue(Result): - attrs = [Attribute('cwe', int, nullable=True), + attrs = [Attribute('cwe', list, nullable=True), Attribute('testid', _string_type, nullable=True), Attribute('location', 'Location'), Attribute('message', 'Message'), @@ -251,8 +253,15 @@ def __init__(self, trace, severity=None, customfields=None): + cwes = [] if cwe is not None: - assert isinstance(cwe, int) + if not isinstance(cwe, int): + cwes = cwe + assert isinstance(cwes, list) + for cwe in cwes: + assert isinstance(cwe, int) + else: + assert isinstance(cwe, int) if testid is not None: assert isinstance(testid, _string_type) assert isinstance(location, Location) @@ -265,7 +274,7 @@ def __init__(self, assert isinstance(severity, _string_type) if customfields is not None: assert isinstance(customfields, CustomFields) - self.cwe = cwe + self.cwe = cwes self.testid = testid self.location = location self.message = message @@ -277,8 +286,11 @@ def __init__(self, @classmethod def from_xml(cls, node): cwe = node.get('cwe') - if cwe is not None: - cwe = int(cwe) + cwe_list = [] + if cwe is not None and cwe is not "": + cwes = cwe.split(',') + for cwe in cwes: + cwe_list.append(int(cwe)) testid = node.get('test-id') location = Location.from_xml(node.find('location')) message = Message.from_xml(node.find('message')) @@ -298,12 +310,18 @@ def from_xml(cls, node): customfields = CustomFields.from_xml(customfields_node) else: customfields = None - return Issue(cwe, testid, location, message, notes, trace, severity, customfields) + return Issue(cwe_list, testid, location, message, notes, trace, severity, customfields) def to_xml(self): node = ET.Element('issue') if self.cwe is not None: - node.set('cwe', str(self.cwe)) + if isinstance(self.cwe, list): + cwe_list = "" + for cwe in self.cwe: + cwe_list += ',' + str(cwe) + node.set('cwe', str(cwe_list[1::])) + else: + node.set('cwe', str(self.cwe)) if self.testid is not None: node.set('test-id', str(self.testid)) node.append(self.message.to_xml()) @@ -339,7 +357,9 @@ def diagnostic(filename, line, column, kind, msg): % (self.location.file.givenpath, self.location.function.name)) if self.cwe: - cwetext = ' [%s]' % self.get_cwe_str() + if isinstance(self.cwe, list): + cwetext = [] + cwetext = self.get_cwe_str() else: cwetext = '' diagnostic(filename=self.location.file.givenpath, @@ -379,12 +399,27 @@ def accept(self, visitor): self.trace.accept(visitor) def get_cwe_str(self): + cwe_list_str = [] if self.cwe is not None: - return 'CWE-%i' % self.cwe + if isinstance(self.cwe, list): + for cwe in self.cwe: + cwe_list_str.append('CWE-%i' % int(cwe)) + else: + cwe_list_str.append('CWE-%i' % int(self.cwe)) + return cwe_list_str + def get_cwe_url(self): + cwe_list_str = [] if self.cwe is not None: - return 'http://cwe.mitre.org/data/definitions/%i.html' % self.cwe + if isinstance(self.cwe, list): + for cwe in self.cwe: + cwe_list_str.append('http://cwe.mitre.org/data/definitions/%i.html' % cwe) + return cwe_list_str + else: + cwe_list_str.append('http://cwe.mitre.org/data/definitions/%i.html' % self.cwe) + return cwe_list_str + class Failure(Result): attrs = [Attribute('failureid', _string_type, nullable=True), diff --git a/tests/parsers/test_clanganalyzer_parser.py b/tests/parsers/test_clanganalyzer_parser.py index c30e7d4..bac41ac 100644 --- a/tests/parsers/test_clanganalyzer_parser.py +++ b/tests/parsers/test_clanganalyzer_parser.py @@ -44,7 +44,7 @@ def test_example_001(self): self.assertEqual(len(a.results), 2) w0 = a.results[0] - self.assertEqual(w0.cwe, None) + self.assertEqual(w0.cwe, []) self.assertEqual(w0.testid, None) self.assertEqual(w0.message.text, "Value stored to 'ret' is never read") diff --git a/tests/parsers/test_cppcheck_parser.py b/tests/parsers/test_cppcheck_parser.py index ec0a22b..41f8d55 100644 --- a/tests/parsers/test_cppcheck_parser.py +++ b/tests/parsers/test_cppcheck_parser.py @@ -40,7 +40,7 @@ def test_example_001(self): self.assertEqual(len(a.results), 7) r0 = a.results[0] self.assertIsInstance(r0, Issue) - self.assertEqual(r0.cwe, None) + self.assertEqual(r0.cwe, []) self.assertEqual(r0.testid, 'uninitvar') self.assertEqual(r0.message.text, 'Uninitialized variable: ret') self.assertEqual(r0.notes, None) diff --git a/tests/parsers/test_gcc_parser.py b/tests/parsers/test_gcc_parser.py index 3fa6de4..8a972c8 100644 --- a/tests/parsers/test_gcc_parser.py +++ b/tests/parsers/test_gcc_parser.py @@ -63,7 +63,7 @@ def test_values_c(self): issue = gcc.parse_warning(line, FUNC_NAME) # Verify the metadata: - self.assertEqual(issue.cwe, None) + self.assertEqual(issue.cwe, []) self.assertEqual(issue.testid, 'unused-result') self.assertIsInstance(issue.location, Location) self.assertIsInstance(issue.location.file, File) diff --git a/tests/test_model.py b/tests/test_model.py index 87033d5..86a8663 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -37,7 +37,7 @@ def make_simple_analysis(self): sut=None, file_=None, stats=None), - results=[Issue(cwe=None, + results=[Issue(cwe=[], testid=None, location=Location(file=File('foo.c', None), function=None, @@ -60,7 +60,7 @@ def make_complex_analysis(self): file_=File(givenpath='foo.c', abspath='/home/david/coding/foo.c'), stats=Stats(wallclocktime=0.4)), - results=[Issue(cwe=681, + results=[Issue(cwe=[681], testid='refcount-too-high', location=Location(file=File(givenpath='foo.c', abspath='/home/david/coding/foo.c'), @@ -127,7 +127,7 @@ def test_creating_simple_analysis(self): self.assertEqual(a.metadata.sut, None) self.assertEqual(a.metadata.file_, None) self.assertEqual(a.metadata.stats, None) - self.assertEqual(w.cwe, None) + self.assertEqual(w.cwe, []) self.assertEqual(w.testid, None) self.assertEqual(w.location.file.givenpath, 'foo.c') self.assertEqual(w.location.file.abspath, None) @@ -150,7 +150,7 @@ def test_creating_complex_analysis(self): self.assertEqual(a.metadata.file_.givenpath, 'foo.c') self.assertEqual(a.metadata.file_.abspath, '/home/david/coding/foo.c') self.assertEqual(a.metadata.stats.wallclocktime, 0.4) - self.assertEqual(w.cwe, 681) + self.assertEqual(w.cwe, [681]) self.assertEqual(w.testid, 'refcount-too-high') self.assertEqual(w.location.file.givenpath, 'foo.c') self.assertEqual(w.location.file.abspath, '/home/david/coding/foo.c') @@ -228,7 +228,7 @@ def test_example_2(self): self.assertEqual(len(a.results), 1) w = a.results[0] self.assertIsInstance(w, Issue) - self.assertEqual(w.cwe, 401) + self.assertEqual(w.cwe, [401,200]) self.assertEqual(w.testid, 'refcount-too-high') self.assertEqual(w.location.file.givenpath, 'examples/python-src-example.c') self.assertEqual(w.location.file.abspath, None) @@ -491,16 +491,16 @@ def compare_hashes(creator): def test_cwe(self): # Verify that the CWE methods are sane: a, w = self.make_complex_analysis() - self.assertIsInstance(w.cwe, int) - self.assertEqual(w.get_cwe_str(), 'CWE-681') + self.assertIsInstance(w.cwe, list) + self.assertEqual(w.get_cwe_str(), ['CWE-681']) self.assertEqual(w.get_cwe_url(), - 'http://cwe.mitre.org/data/definitions/681.html') + ['http://cwe.mitre.org/data/definitions/681.html']) # Verify that they are sane for a warning without a CWE: a, w = self.make_simple_analysis() - self.assertEqual(w.cwe, None) - self.assertEqual(w.get_cwe_str(), None) - self.assertEqual(w.get_cwe_url(), None) + self.assertEqual(w.cwe, []) + self.assertEqual(w.get_cwe_str(), []) + self.assertEqual(w.get_cwe_url(), []) def test_fixup_paths(self): # Verify that Report.fixup_files() can make paths absolute: @@ -535,7 +535,7 @@ def test_gcc_output(self): w.write_as_gcc_output(output) self.assertMultiLineEqual(output.getvalue(), ("foo.c: In function 'bar':\n" - "foo.c:10:15: warning: something bad involving pointers [CWE-681]\n" + "foo.c:10:15: warning: something bad involving pointers['CWE-681']\n" "here is some explanatory text\n" "foo.c:7:12: note: first we do this\n" "foo.c:8:10: note: then we do that\n"