Skip to content

Commit 97c0e27

Browse files
Jino-Tclaude
andauthored
feat(coverity-api): add support for RESOURCE_LEAK quality findings (#14749)
Extend the Coverity API parser to import findings with checker RESOURCE_LEAK and displayIssueKind Quality, in addition to the existing Security findings. Update tests to reflect the new counts and add assertions covering RESOURCE_LEAK field values. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0f800eb commit 97c0e27

3 files changed

Lines changed: 139 additions & 16 deletions

File tree

dojo/tools/coverity_api/parser.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ def get_findings(self, file, test):
2626

2727
items = []
2828
for issue in tree["viewContentsV1"]["rows"]:
29-
# get only security findings
30-
if issue.get("displayIssueKind") != "Security":
29+
# get security findings and Quality RESOURCE_LEAK findings
30+
if not (
31+
issue.get("displayIssueKind") == "Security"
32+
or (issue.get("displayIssueKind") == "Quality" and issue.get("checker") == "RESOURCE_LEAK")
33+
):
3134
continue
3235

3336
description_formated = "\n".join(
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"viewContentsV1": {
3+
"offset": 0,
4+
"totalRows": 3,
5+
"columns": [
6+
{"name": "cid", "label": "CID"},
7+
{"name": "displayType", "label": "Type"},
8+
{"name": "displayImpact", "label": "Impact"},
9+
{"name": "status", "label": "Status"},
10+
{"name": "firstDetected", "label": "First Detected"},
11+
{"name": "owner", "label": "Owner"},
12+
{"name": "classification", "label": "Classification"},
13+
{"name": "severity", "label": "Severity"},
14+
{"name": "action", "label": "Action"},
15+
{"name": "displayComponent", "label": "Component"},
16+
{"name": "displayCategory", "label": "Category"},
17+
{"name": "displayFile", "label": "File"},
18+
{"name": "displayFunction", "label": "Function"},
19+
{"name": "displayIssueKind", "label": "Issue Kind"},
20+
{"name": "lastDetected", "label": "Last Snapshot Date"},
21+
{"name": "checker", "label": "Checker"},
22+
{"name": "occurrenceCount", "label": "Count"},
23+
{"name": "cwe", "label": "CWE"},
24+
{"name": "lastTriaged", "label": "Last Triaged"},
25+
{"name": "externalReference", "label": "External Reference"},
26+
{"name": "lastDetectedTarget", "label": "Last Snapshot Target"}
27+
],
28+
"rows": [
29+
{
30+
"cid": 10001,
31+
"displayType": "Dereference null return value",
32+
"displayImpact": "Medium",
33+
"status": "New",
34+
"firstDetected": "03/23/21",
35+
"owner": "Unassigned",
36+
"classification": "Unclassified",
37+
"severity": "Unspecified",
38+
"action": "Undecided",
39+
"displayComponent": "Other",
40+
"displayCategory": "Null pointer dereferences",
41+
"displayFile": "Aaaaaa/Bbbbbb/Cccccc/Dddddd.rs",
42+
"displayFunction": "Eeeeee.Ffffff.Gggggg::Hhhhhh",
43+
"displayIssueKind": "Quality",
44+
"lastDetected": "04/07/21",
45+
"checker": "NULL_RETURNS",
46+
"occurrenceCount": 1,
47+
"cwe": 476,
48+
"lastTriaged": "",
49+
"externalReference": "",
50+
"lastDetectedTarget": ""
51+
},
52+
{
53+
"cid": 10002,
54+
"displayType": "Explicit null dereferenced",
55+
"displayImpact": "Medium",
56+
"status": "New",
57+
"firstDetected": "03/23/21",
58+
"owner": "Unassigned",
59+
"classification": "Unclassified",
60+
"severity": "Unspecified",
61+
"action": "Undecided",
62+
"displayComponent": "Other",
63+
"displayCategory": "Null pointer dereferences",
64+
"displayFile": "Iiiiii/Jjjjjj/Kkkkkk/Llllll.rs",
65+
"displayFunction": "Mmmmmm.Nnnnnn.Oooooo::Pppppp",
66+
"displayIssueKind": "Quality",
67+
"lastDetected": "04/07/21",
68+
"checker": "FORWARD_NULL",
69+
"occurrenceCount": 2,
70+
"cwe": 476,
71+
"lastTriaged": "",
72+
"externalReference": "",
73+
"lastDetectedTarget": ""
74+
},
75+
{
76+
"cid": 10003,
77+
"displayType": "Dereference null return (stat)",
78+
"displayImpact": "Medium",
79+
"status": "New",
80+
"firstDetected": "03/23/21",
81+
"owner": "Unassigned",
82+
"classification": "Unclassified",
83+
"severity": "Unspecified",
84+
"action": "Undecided",
85+
"displayComponent": "Other",
86+
"displayCategory": "Null pointer dereferences",
87+
"displayFile": "Qqqqqq/Rrrrrr/Ssssss/Tttttt.rs",
88+
"displayFunction": "Uuuuuu.Vvvvvv.Wwwwww::Xxxxxx",
89+
"displayIssueKind": "Quality",
90+
"lastDetected": "04/07/21",
91+
"checker": "NULL_RETURNS",
92+
"occurrenceCount": 1,
93+
"cwe": 476,
94+
"lastTriaged": "",
95+
"externalReference": "",
96+
"lastDetectedTarget": ""
97+
}
98+
]
99+
}
100+
}

unittests/tools/test_coverity_api_parser.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ def test_parse_no_findings(self):
1919
self.assertEqual(0, len(findings))
2020

2121
def test_parse_only_quality(self):
22-
"""This report only have quality findings"""
23-
with (get_unit_tests_scans_path("coverity_api") / "only_quality.json").open(encoding="utf-8") as testfile:
22+
"""Non-RESOURCE_LEAK quality findings are excluded"""
23+
with (get_unit_tests_scans_path("coverity_api") / "only_non_resource_leak_quality.json").open(encoding="utf-8") as testfile:
2424
parser = CoverityApiParser()
2525
findings = parser.get_findings(testfile, Test())
2626
self.assertEqual(0, len(findings))
@@ -30,11 +30,21 @@ def test_parse_some_findings(self):
3030
parser = CoverityApiParser()
3131
findings = parser.get_findings(testfile, Test())
3232
self.assertIsInstance(findings, list)
33-
self.assertEqual(1, len(findings))
33+
self.assertEqual(6, len(findings))
3434
with self.subTest(i=0):
35-
finding = findings[0]
35+
finding = findings[0] # first RESOURCE_LEAK finding
3636
self.assertTrue(finding.active)
37-
self.assertFalse(finding.verified) # this one is marked as new ("status": "New")
37+
self.assertFalse(finding.verified)
38+
self.assertEqual("Resource leak", finding.title)
39+
self.assertEqual("High", finding.severity)
40+
self.assertEqual(404, finding.cwe)
41+
self.assertEqual("Wdkrtgthhl/Llwfzgphzw/Fashvkaxzx/Okkfacqsxw.rs", finding.file_path)
42+
self.assertEqual(datetime.date(2021, 3, 23), finding.date)
43+
self.assertEqual(22480, finding.unique_id_from_tool)
44+
with self.subTest(i=4):
45+
finding = findings[4] # security finding
46+
self.assertTrue(finding.active)
47+
self.assertFalse(finding.verified)
3848
self.assertEqual("Risky cryptographic hashing function", finding.title)
3949
self.assertEqual("Medium", finding.severity)
4050
self.assertEqual(328, finding.cwe)
@@ -47,9 +57,9 @@ def test_parse_few_findings_triaged_as_bug(self):
4757
parser = CoverityApiParser()
4858
findings = parser.get_findings(testfile, Test())
4959
self.assertIsInstance(findings, list)
50-
self.assertEqual(1, len(findings))
51-
with self.subTest(i=0):
52-
finding = findings[0]
60+
self.assertEqual(13, len(findings))
61+
with self.subTest(i=1):
62+
finding = findings[1] # security finding (triaged as bug)
5363
self.assertTrue(finding.active)
5464
self.assertTrue(finding.verified)
5565
self.assertEqual("HTTP header injection", finding.title)
@@ -64,9 +74,19 @@ def test_parse_some_findings_mitigated(self):
6474
parser = CoverityApiParser()
6575
findings = parser.get_findings(testfile, Test())
6676
self.assertIsInstance(findings, list)
67-
self.assertEqual(20, len(findings))
77+
self.assertEqual(25, len(findings))
6878
with self.subTest(i=0):
69-
finding = findings[0] # this one is dismissed as a false positive
79+
finding = findings[0] # RESOURCE_LEAK finding (active, status New)
80+
self.assertTrue(finding.active)
81+
self.assertFalse(finding.verified)
82+
self.assertEqual("Resource leak", finding.title)
83+
self.assertEqual("High", finding.severity)
84+
self.assertEqual(404, finding.cwe)
85+
self.assertEqual("Vzfkposilb/Ejgmugyeam/Ekcbsjzuiq/Isjhjabnfe.rs", finding.file_path)
86+
self.assertEqual(datetime.date(2021, 3, 31), finding.date)
87+
self.assertEqual(22496, finding.unique_id_from_tool)
88+
with self.subTest(i=2):
89+
finding = findings[2] # this one is dismissed as a false positive
7090
self.assertFalse(finding.active)
7191
self.assertTrue(finding.verified)
7292
self.assertTrue(finding.false_p)
@@ -76,8 +96,8 @@ def test_parse_some_findings_mitigated(self):
7696
self.assertEqual("Pfozpmtueo/Vtoqmbvmzf/Noxacjclcz/Aymctwefbi.rs", finding.file_path)
7797
self.assertEqual(datetime.date(2021, 3, 26), finding.date)
7898
self.assertEqual(22486, finding.unique_id_from_tool)
79-
with self.subTest(i=10):
80-
finding = findings[10]
99+
with self.subTest(i=12):
100+
finding = findings[12]
81101
self.assertFalse(finding.active)
82102
self.assertTrue(finding.verified)
83103
self.assertEqual("Use of hard-coded password", finding.title)
@@ -86,8 +106,8 @@ def test_parse_some_findings_mitigated(self):
86106
self.assertEqual("Hvsilgzkwz/Lhmxrchybr/Edcoanzncg/Oowieyoxvn.rs", finding.file_path)
87107
self.assertEqual(datetime.date(2021, 3, 15), finding.date)
88108
self.assertEqual(22421, finding.unique_id_from_tool)
89-
with self.subTest(i=19):
90-
finding = findings[19]
109+
with self.subTest(i=23):
110+
finding = findings[23]
91111
self.assertFalse(finding.active)
92112
self.assertTrue(finding.verified)
93113
self.assertEqual("Cross-site scripting", finding.title)

0 commit comments

Comments
 (0)