Skip to content
37 changes: 23 additions & 14 deletions pythx/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,20 +458,29 @@ def report(config, staging, uuid):
resp = c.report(uuid)

file_to_issue = defaultdict(list)

for issue in resp.issues:
source_locs = [loc.source_map.split(":") for loc in issue.locations]
source_locs = [(int(o), int(l), int(i)) for o, l, i in source_locs]
for offset, _, file_idx in source_locs:
if resp.source_list and file_idx > 0:
filename = resp.source_list[file_idx]
line, column = get_source_location_by_offset(filename, int(offset))
else:
filename = "Unknown"
line, column = 0, 0
file_to_issue[filename].append(
(line, column, issue.swc_title, issue.severity, issue.description_short)
)
for rep in resp.issue_reports:
for issue in rep.issues:
source_locs = [loc.source_map.split(":") for loc in issue.locations]
source_locs = [(int(o), int(l), int(i)) for o, l, i in source_locs]
for offset, _, file_idx in source_locs:
if rep.source_list and file_idx >= 0:
filename = rep.source_list[file_idx]
try:
line, column = get_source_location_by_offset(filename, int(offset))
except FileNotFoundError:
line, column = 0, 0
else:
filename = "Unknown"
line, column = 0, 0
file_to_issue[filename].append(
(
line,
column,
issue.swc_title,
issue.severity,
issue.description_short,
)
)

for filename, data in file_to_issue.items():
click.echo("Report for {}".format(filename))
Expand Down
2 changes: 1 addition & 1 deletion pythx/models/response/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pythx.models.response.analysis_list import AnalysisListResponse
from pythx.models.response.analysis_status import AnalysisStatusResponse
from pythx.models.response.analysis_submission import AnalysisSubmissionResponse
from pythx.models.response.detected_issues import DetectedIssuesResponse
from pythx.models.response.detected_issues import IssueReport, DetectedIssuesResponse
from pythx.models.response.auth_login import AuthLoginResponse
from pythx.models.response.auth_logout import AuthLogoutResponse
from pythx.models.response.auth_refresh import AuthRefreshResponse
Expand Down
133 changes: 104 additions & 29 deletions pythx/models/response/detected_issues.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""This module contains the response models for the detected issues endpoint and a report helper."""
import json
from typing import Any, Dict, List

from pythx.models.exceptions import ResponseValidationError
from pythx.models.response.base import BaseResponse
from pythx.models.response.issue import Issue, SourceFormat, SourceType
from pythx.models.util import resolve_schema


class DetectedIssuesResponse(BaseResponse):
"""The API response domain model for a report of the detected issues."""

with open(resolve_schema(__file__, "detected-issues.json")) as sf:
schema = json.load(sf)
class IssueReport:
"""The API response domain model for an issues report object."""

def __init__(
self,
Expand All @@ -28,16 +27,11 @@ def __init__(

@classmethod
def from_dict(cls, d):
"""Create the response domain model from a dict.

This also validates the dict's schema and raises a :code:`ResponseValidationError`
if any required keys are missing or the data is malformed.
"""Create the issue report domain model from a dict.

:param d: The dict to deserialize from
:return: The domain model with the data from :code:`d` filled in
:return: The domain model with the data from :code:`d` filled in
"""
cls.validate(d)
d = d[0]
return cls(
issues=[Issue.from_dict(i) for i in d["issues"]],
source_type=SourceType(d["sourceType"]),
Expand All @@ -47,29 +41,20 @@ def from_dict(cls, d):
)

def to_dict(self):
"""Serialize the reponse model to a Python dict.
"""Serialize the issue report domain model to a Python dict.

:return: A dict holding the request model data
"""
d = [
{
"issues": [i.to_dict() for i in self.issues],
"sourceType": self.source_type,
"sourceFormat": self.source_format,
"sourceList": self.source_list,
"meta": self.meta_data,
}
]
self.validate(d)
d = {
"issues": [i.to_dict() for i in self.issues],
"sourceType": self.source_type,
"sourceFormat": self.source_format,
"sourceList": self.source_list,
"meta": self.meta_data,
}
return d

def __contains__(self, key: str):
if not type(key) == str:
raise ValueError(
"Expected SWC ID of type str but got {} of type {}".format(
key, type(key)
)
)
return any(map(lambda i: i.swc_id == key, self.issues))

def __len__(self):
Expand All @@ -87,3 +72,93 @@ def __setitem__(self, key, value):

def __delitem__(self, key):
del self.issues[key]


class DetectedIssuesResponse(BaseResponse):
"""The API response domain model for a report of the detected issues."""

with open(resolve_schema(__file__, "detected-issues.json")) as sf:
schema = json.load(sf)

def __init__(self, issue_reports: List[IssueReport]) -> None:
self.issue_reports = issue_reports

@classmethod
def from_dict(cls, d: Dict):
"""Create the response domain model from a dict.

This also validates the dict's schema and raises a :code:`ResponseValidationError`
if any required keys are missing or the data is malformed.

:param d: The List to deserialize from
:return: The domain model with the data from :code:`d` filled in
"""

if type(d) == list:
cls.validate(d)
d = {"issueReports": d}
elif type(d) == dict:
if d.get("issueReports") is None:
raise ResponseValidationError(
"Cannot create DetectedIssuesResponse object from invalid dictionary d: {}".format(
d
)
)

cls.validate(d["issueReports"])
else:
raise ResponseValidationError(
"Expected list or dict but got {} of type {}".format(d, type(d))
)

return cls(issue_reports=[IssueReport.from_dict(i) for i in d["issueReports"]])

def to_dict(self):
"""Serialize the reponse model to a Python dict.

:return: A dict holding the request model data
"""
d = {"issueReports": [report.to_dict() for report in self.issue_reports]}
self.validate(d["issueReports"])
return d

def to_json(self):
"""Serialize the model to JSON format.

Internally, this method is using the :code:`to_dict` method.

:return: A JSON string holding the model's data
"""
return json.dumps([report.to_dict() for report in self.issue_reports])

def __contains__(self, key: str):
if not type(key) == str:
raise ValueError(
"Expected SWC ID of type str but got {} of type {}".format(
key, type(key)
)
)
for report in self.issue_reports:
if key in report:
return True
return False

def __iter__(self):
for report in self.issue_reports:
for issue in report:
yield issue

def __len__(self):
total_detected_issues = 0
for report in self.issue_reports:
total_detected_issues += len(report)
return total_detected_issues

def __getitem__(self, key: int):
return self.issue_reports[key]

def __setitem__(self, key: int, value: IssueReport):
self.issue_reports[key] = value

def __delitem__(self, key: int):
del self.issue_reports[key]
58 changes: 32 additions & 26 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
AuthRefreshResponse,
DetectedIssuesResponse,
Issue,
IssueReport,
OASResponse,
Severity,
SourceFormat,
Expand Down Expand Up @@ -363,35 +364,40 @@
# DETECTED ISSUES
DETECTED_ISSUES_REQUEST_DICT = {"uuid": UUID_1}
DETECTED_ISSUES_REQUEST_OBJECT = DetectedIssuesRequest(uuid=UUID_1)
DETECTED_ISSUES_RESPONSE_DICT = [
{
"issues": [
{
"swcID": SWC_ID,
"swcTitle": SWC_TITLE,
"description": {"head": DESCRIPTION_HEAD, "tail": DESCRIPTION_TAIL},
"severity": SEVERITY,
"locations": [
{
"sourceMap": SOURCE_MAP,
"sourceType": SOURCE_TYPE,
"sourceFormat": SOURCE_FORMAT,
"sourceList": SOURCE_LIST,
}
],
"extra": {},
}
],
"sourceType": SOURCE_TYPE,
"sourceFormat": SOURCE_FORMAT,
"sourceList": SOURCE_LIST,
"meta": {},
}
]
DETECTED_ISSUES_RESPONSE_OBJECT = DetectedIssuesResponse(
ISSUE_REPORT_DICT = {
"issues": [
{
"swcID": SWC_ID,
"swcTitle": SWC_TITLE,
"description": {"head": DESCRIPTION_HEAD, "tail": DESCRIPTION_TAIL},
"severity": SEVERITY,
"locations": [
{
"sourceMap": SOURCE_MAP,
"sourceType": SOURCE_TYPE,
"sourceFormat": SOURCE_FORMAT,
"sourceList": SOURCE_LIST,
}
],
"extra": {},
}
],
"sourceType": SOURCE_TYPE,
"sourceFormat": SOURCE_FORMAT,
"sourceList": SOURCE_LIST,
"meta": {},
}

ISSUE_REPORT_OBJECT = IssueReport(
issues=[ISSUE_OBJECT],
source_type=SourceType.RAW_BYTECODE,
source_format=SourceFormat.EVM_BYZANTIUM_BYTECODE,
source_list=SOURCE_LIST,
meta_data={},
)

DETECTED_ISSUES_RESPONSE_DICT = {"issueReports": [ISSUE_REPORT_DICT]}

DETECTED_ISSUES_RESPONSE_OBJECT = DetectedIssuesResponse(
issue_reports=[ISSUE_REPORT_OBJECT]
)
22 changes: 16 additions & 6 deletions tests/test_api_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,25 @@ def test_parse_detected_issues_response():
)
assert_response_middleware_hook(model)
assert (
model.issues[0].to_dict()
== testdata.DETECTED_ISSUES_RESPONSE_DICT[0]["issues"][0]
model.issue_reports[0].issues[0].to_dict()
== testdata.DETECTED_ISSUES_RESPONSE_DICT["issueReports"][0]["issues"][0]
)
assert model.source_type == testdata.DETECTED_ISSUES_RESPONSE_DICT[0]["sourceType"]
assert (
model.source_format == testdata.DETECTED_ISSUES_RESPONSE_DICT[0]["sourceFormat"]
model.issue_reports[0].source_type
== testdata.DETECTED_ISSUES_RESPONSE_DICT["issueReports"][0]["sourceType"]
)
assert (
model.issue_reports[0].source_format
== testdata.DETECTED_ISSUES_RESPONSE_DICT["issueReports"][0]["sourceFormat"]
)
assert (
model.issue_reports[0].source_list
== testdata.DETECTED_ISSUES_RESPONSE_DICT["issueReports"][0]["sourceList"]
)
assert (
model.issue_reports[0].meta_data
== testdata.DETECTED_ISSUES_RESPONSE_DICT["issueReports"][0]["meta"]
)
assert model.source_list == testdata.DETECTED_ISSUES_RESPONSE_DICT[0]["sourceList"]
assert model.meta_data == testdata.DETECTED_ISSUES_RESPONSE_DICT[0]["meta"]


def test_parse_login_response():
Expand Down
12 changes: 6 additions & 6 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,12 @@ def test_report():
client = get_client([testdata.DETECTED_ISSUES_RESPONSE_DICT])
resp = client.report(uuid=testdata.UUID_1)
assert type(resp) == respmodels.DetectedIssuesResponse
assert resp.source_type == testdata.SOURCE_TYPE
assert resp.source_format == testdata.SOURCE_FORMAT
assert resp.source_list == testdata.SOURCE_LIST
assert resp.meta_data == {}
assert len(resp.issues) == 1
issue = resp.issues[0]
assert resp.issue_reports[0].source_type == testdata.SOURCE_TYPE
assert resp.issue_reports[0].source_format == testdata.SOURCE_FORMAT
assert resp.issue_reports[0].source_list == testdata.SOURCE_LIST
assert resp.issue_reports[0].meta_data == {}
assert len(resp) == 1
issue = resp.issue_reports[0].issues[0]
assert issue.swc_id == testdata.SWC_ID
assert issue.swc_title == testdata.SWC_TITLE
assert issue.description_short == testdata.DESCRIPTION_HEAD
Expand Down
Loading