Skip to content

Commit

Permalink
Use artifact in context
Browse files Browse the repository at this point in the history
* Pass the artifact to the location instead of just file name
* Load the snippet with the initialization of the location

Signed-off-by: Eric Brown <[email protected]>
  • Loading branch information
ericwb committed Feb 5, 2024
1 parent 50157d8 commit e42c636
Show file tree
Hide file tree
Showing 29 changed files with 135 additions and 248 deletions.
55 changes: 0 additions & 55 deletions precli/core/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@
class Location:
def __init__(
self,
file_name: str = None,
url: str = None,
node: Node = None,
start_line: int = 0,
end_line: int = -1,
start_column: int = 1,
end_column: int = -1,
snippet: str = None,
):
self._file_name = file_name
self._url = url
if node is not None:
self._start_line = node.start_point[0] + 1
self._start_column = node.start_point[1]
Expand All @@ -27,37 +22,6 @@ def __init__(
self._start_column = start_column
# TODO: default to end of line
self._end_column = end_column
self._snippet = snippet

@property
def file_name(self) -> str:
"""
Name of the file.
:return: file name
:rtype: str
"""
return self._file_name

@property
def url(self) -> str:
"""
If the original target was given as a URL, this
property will return that address.
:return: URL
:rtype: str
"""
return self._url

@url.setter
def url(self, url: str):
"""
Set the file location as a URL
:param str url: file network location
"""
self._url = url

@property
def start_line(self) -> int:
Expand Down Expand Up @@ -98,22 +62,3 @@ def end_column(self) -> int:
:rtype: int
"""
return self._end_column

@property
def snippet(self) -> str:
"""
Snippet of context of the code.
:return: snippet of context
:rtype: str
"""
return self._snippet

@snippet.setter
def snippet(self, snippet):
"""
Set the code context snippet.
:param str snippet: context snippet
"""
self._snippet = snippet
48 changes: 32 additions & 16 deletions precli/core/result.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
from precli.core.artifact import Artifact
from precli.core.fix import Fix
from precli.core.kind import Kind
from precli.core.level import Level
from precli.core.linecache import LineCache
from precli.core.location import Location
from precli.core.suppression import Suppression
from precli.rules import Rule
Expand All @@ -11,14 +13,17 @@ class Result:
def __init__(
self,
rule_id: str,
artifact: Artifact = None,
kind: Kind = Kind.FAIL,
level: Level = None,
location: Location = None,
message: str = None,
fixes: list[Fix] = None,
suppression: Suppression = None,
snippet: str = None,
):
self._rule_id = rule_id
self._artifact = artifact
self._kind = kind
default_config = Rule.get_by_id(self._rule_id).default_config
self._rank = default_config.rank
Expand All @@ -34,6 +39,17 @@ def __init__(
self._fixes = fixes if fixes is not None else []
self._suppression = suppression

if snippet is not None:
self._snippet = snippet
else:
linecache = LineCache(
artifact.file_name,
artifact.contents.decode(),
)
self._snippet = ""
for i in range(location.start_line - 1, location.end_line + 2):
self._snippet += linecache.getline(i)

@property
def rule_id(self) -> str:
"""
Expand All @@ -48,24 +64,14 @@ def rule_id(self) -> str:
return self._rule_id

@property
def source_language(self) -> str:
def artifact(self) -> Artifact:
"""
The source language.
Artifact, typically the file.
:return: language of the source code
:rtype: str
:return: the artifact
:rtype: Artifact
"""
match (self._rule_id[:2]):
case "GO":
return "go"
case "JV":
return "java"
case "PY":
return "python"
case "RB":
return "ruby"
case "RS":
return "rust"
return self._artifact

@property
def location(self) -> Location:
Expand Down Expand Up @@ -159,3 +165,13 @@ def suppression(self, suppression):
:param Suppression suppression: suppression
"""
self._suppression = suppression

@property
def snippet(self) -> str:
"""
Snippet of context of the code.
:return: snippet of context
:rtype: str
"""
return self._snippet
13 changes: 2 additions & 11 deletions precli/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from tree_sitter import Node

from precli.core.artifact import Artifact
from precli.core.linecache import LineCache
from precli.core.location import Location
from precli.core.result import Result
from precli.core.suppression import Suppression
Expand Down Expand Up @@ -78,22 +77,14 @@ def parse(self, artifact: Artifact) -> list[Result]:
:rtype: list
"""
self.results = []
self.context = {"file_name": artifact.file_name}
self.context = {"artifact": artifact}
if artifact.contents is None:
with open(artifact.file_name, "rb") as fdata:
artifact.contents = fdata.read()
tree = self.tree_sitter_parser.parse(artifact.contents)
self.visit([tree.root_node])

linecache = LineCache(artifact.file_name, artifact.contents.decode())

for result in self.results:
start = result.location.start_line - 1
stop = result.location.end_line + 2
result.location.snippet = ""
for i in range(start, stop):
result.location.snippet += linecache.getline(i)

suppression = self.suppressions.get(result.location.start_line)
if suppression and result.rule_id in suppression.rules:
result.suppression = suppression
Expand Down Expand Up @@ -161,7 +152,7 @@ def visit_ERROR(self, nodes: list[Node]):
raise SyntaxError(
"Syntax error while parsing file.",
(
self.context["file_name"],
self.context["artifact"].file_name,
err_node.start_point[0] + 1,
err_node.start_point[1] + 1,
err_node.text.decode(errors="ignore"),
Expand Down
18 changes: 9 additions & 9 deletions precli/renderers/detailed.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ def render(self, results: list[Result], metrics: Metrics):
emoji = ":information-emoji: "
style = "blue"

if result.location.url is not None:
file_name = result.location.url
if result.artifact.uri is not None:
file_name = result.artifact.uri
else:
file_name = result.location.file_name
file_name = result.artifact.file_name

self.console.print(
f"{emoji} {result.level.name.title()} on line "
Expand All @@ -59,8 +59,8 @@ def render(self, results: list[Result], metrics: Metrics):

line_offset = result.location.start_line - 2
code = syntax.Syntax(
result.location.snippet,
result.source_language,
result.snippet,
result.artifact.language,
line_numbers=True,
start_line=line_offset + 1,
line_range=(
Expand Down Expand Up @@ -96,13 +96,13 @@ def render(self, results: list[Result], metrics: Metrics):
before = 0
else:
line_before = linecache.getline(
filename=result.location.file_name,
filename=result.artifact.file_name,
lineno=start_line - 1,
)
before = 1

code = linecache.getline(
filename=result.location.file_name,
filename=result.artifact.file_name,
lineno=start_line,
)

Expand All @@ -111,7 +111,7 @@ def render(self, results: list[Result], metrics: Metrics):
after = 0
else:
line_after = linecache.getline(
filename=result.location.file_name,
filename=result.artifact.file_name,
lineno=start_line + 1,
)
after = 1
Expand All @@ -127,7 +127,7 @@ def render(self, results: list[Result], metrics: Metrics):

code = syntax.Syntax(
code,
result.source_language,
result.artifact.language,
line_numbers=True,
line_range=(start_line - before, end_line + after),
highlight_lines=highlight_lines,
Expand Down
6 changes: 3 additions & 3 deletions precli/renderers/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def render(self, results: list[Result], metrics: Metrics):
for result in results:
rule = Rule.get_by_id(result.rule_id)

if result.location.url is not None:
file_name = result.location.url
if result.artifact.uri is not None:
file_name = result.artifact.uri
else:
file_name = result.location.file_name
file_name = result.artifact.file_name

results_json["results"].append(
{
Expand Down
8 changes: 4 additions & 4 deletions precli/renderers/plain.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ def render(self, results: list[Result], metrics: Metrics):
f"{rule.id}: {rule.cwe.name}",
)

if result.location.url is not None:
file_name = result.location.url
if result.artifact.uri is not None:
file_name = result.artifact.uri
else:
file_name = result.location.file_name
file_name = result.artifact.file_name

# TODO(ericwb): replace hardcoded <module> with actual scope
self.console.print(
f' File "{file_name}", line '
f"{result.location.start_line}, in <module>",
)
code_lines = result.location.snippet.splitlines(keepends=True)
code_lines = result.snippet.splitlines(keepends=True)
code_line = code_lines[1] if len(code_lines) > 1 else code_lines[0]
underline_width = (
result.location.end_column - result.location.start_column
Expand Down
8 changes: 3 additions & 5 deletions precli/rules/go/stdlib/crypto_weak_cipher.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
==================================================================
Use of a Broken or Risky Cryptographic Algorithm in Crypto Package
Expand Down Expand Up @@ -158,10 +158,8 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
location=Location(
file_name=context["file_name"],
node=call.function_node,
),
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name),
fixes=fixes,
Expand Down
14 changes: 5 additions & 9 deletions precli/rules/go/stdlib/crypto_weak_hash.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
=========================================
Reversible One Way Hash in Crypto Package
Expand Down Expand Up @@ -106,10 +106,8 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
location=Location(
file_name=context["file_name"],
node=call.function_node,
),
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
fixes=fixes,
Expand All @@ -126,10 +124,8 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
location=Location(
file_name=context["file_name"],
node=call.function_node,
),
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
fixes=fixes,
Expand Down
14 changes: 5 additions & 9 deletions precli/rules/go/stdlib/crypto_weak_key.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
================================================================
Inadequate Encryption Strength Using Weak Keys in Crypto Package
Expand Down Expand Up @@ -125,10 +125,8 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
location=Location(
file_name=context["file_name"],
node=argument.identifier_node,
),
artifact=context["artifact"],
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format("DSA", 2048),
fixes=fixes,
Expand All @@ -147,10 +145,8 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
location=Location(
file_name=context["file_name"],
node=argument.node,
),
artifact=context["artifact"],
location=Location(node=argument.node),
level=Level.ERROR if bits <= 1024 else Level.WARNING,
message=self.message.format("RSA", 2048),
fixes=fixes,
Expand Down
Loading

0 comments on commit e42c636

Please sign in to comment.