Skip to content

Commit

Permalink
wip(label-tool): Implement some more rules
Browse files Browse the repository at this point in the history
  • Loading branch information
whisperity committed Jul 9, 2024
1 parent 018f074 commit 7210e00
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 10 deletions.
19 changes: 16 additions & 3 deletions scripts/labels/invariant_check/rules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,28 @@
This package contains the implementation for individually executable invariant
verification rules.
"""
from typing import List, Type

from .base import Base
from .guideline_implies_profile_security import \
GuidelineImpliesProfileSecurity
from .guideline_requires_rule_number_annotation import \
GuidelineRequiresRuleNumberAnnotation
from .guideline_rule_number_annotation_requires_guideline import \
GuidelineRuleNumberAnnotationRequiresGuideline
from .profile_default_subseteq_sensitive_subseteq_extreme \
import ProfileDefaultSubsetEqSensitiveSubsetEqExtreme
from .profile_no_alpha_checkers_in_production import \
ProfileNoAlphaCheckersInProduction

__all__ = [
"Base",
"GuidelineImpliesProfileSecurity",
"GuidelineRequiresRuleNumberAnnotation",
"GuidelineRuleNumberAnnotationRequiresGuideline",
"ProfileDefaultSubsetEqSensitiveSubsetEqExtreme",
"ProfileNoAlphaCheckersInProduction",
]

rules_visible_to_user = [
ProfileDefaultSubsetEqSensitiveSubsetEqExtreme,
]
rules_visible_to_user: List[Type[Base]] = \
[globals()[cls_name] for cls_name in sorted(__all__) if cls_name != "Base"]
10 changes: 9 additions & 1 deletion scripts/labels/invariant_check/rules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ class Base:
supports_fixes = False

@classmethod
def check(cls, labels: MultipleLabels, checker: str) \
def supports_analyser(cls, analyser: str) -> bool:
"""
Returns whether the current guideline is applicable to checkers of
`analyser`.
"""
return True

@classmethod
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[FixAction]]:
"""
Performs an implementation-specific routine that returns the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
from typing import List, Set, Tuple

from ...checker_labels import MultipleLabels
from ... import fixit
from .base import Base


class GuidelineImpliesProfileSecurity(Base):
kind = "guideline.implies_profile_security"
description = """
Ensures that checkers with a "guideline" label corresponding to a published
security guideline (e.g., SEI-CERT) are added to the 'security' profile.
""".replace('\n', ' ')
supports_fixes = True

# Only the following guidelines will trigger the implication.
interesting_guidelines: Set[str] = {"sei-cert",
}

@classmethod
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[fixit.FixAction]]:
guidelines: Set[str] = set(labels[checker].get("guideline", list()))
if not guidelines & cls.interesting_guidelines:
return True, []

profiles: Set[str] = set(labels[checker].get("profile", list()))
missing_profiles = {"security"} - profiles
return not missing_profiles, \
[fixit.AddLabelAction("profile:%s" % (profile))
for profile in missing_profiles]
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
from typing import List, Set, Tuple

from ...checker_labels import MultipleLabels
from ...output import log, coloured, emoji
from ... import fixit
from .base import Base


class GuidelineRequiresRuleNumberAnnotation(Base):
kind = "guideline.requires_rule_number_annotation"
description = """
Checks that checkers with a "guideline" label corresponding to a published
security guideline (e.g., SEI-CERT) must be labelled with a
"<guideline-name>:<rule-number>" label as well.
""".replace('\n', ' ')
supports_fixes = False

# Only the following guidelines will trigger the check.
interesting_guidelines: Set[str] = {"sei-cert",
}

@classmethod
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[fixit.FixAction]]:
guidelines: Set[str] = set(labels[checker].get("guideline", list()))

failed: List[str] = list()
for guideline in (guidelines & cls.interesting_guidelines):
if not labels[checker].get(guideline, list()):
log("%s%s: %s/%s - \"%s\" without \"%s\"",
emoji(":police_car_light: "),
coloured("RULE VIOLATION", "red"),
analyser, checker,
coloured("guideline:%s" % guideline, "green"),
coloured("%s:<RULE-NUMBER>" % guideline, "red"),
)
failed.append(guideline)

return not failed, []
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
from typing import List, Set, Tuple

from ...checker_labels import MultipleLabels
from ...output import log, coloured, emoji
from ... import fixit
from .base import Base


class GuidelineRuleNumberAnnotationRequiresGuideline(Base):
kind = "guideline.rule_number_annotation_requires_guideline"
description = """
Ensures that checkers with a "<guideline-name>:<rule-number>" labels for a
published security guideline (e.g., SEI-CERT) must be labelled with a
"guideline:<guideline-name>" label as well.
""".replace('\n', ' ')
supports_fixes = True

# Only the following guidelines will trigger the check.
interesting_guidelines: Set[str] = {"sei-cert",
}

@classmethod
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[fixit.FixAction]]:
labelled_guidelines: Set[str] = set(labels[checker]
.get("guideline", list()))
missing_guidelines: List[str] = list()

for guideline in cls.interesting_guidelines:
guideline_rule_annotations = set(labels[checker]
.get(guideline, list()))
if guideline_rule_annotations and \
guideline not in labelled_guidelines:
missing_guidelines.append(guideline)
log("%s%s: %s/%s - \"%s\" without \"%s\"",
emoji(":police_car_light: "),
coloured("RULE VIOLATION", "red"),
analyser, checker,
"\", \"".join((
coloured("%s:%s" % (guideline, rule_annotation),
"green")
for rule_annotation in guideline_rule_annotations
)),
coloured("guideline:%s" % (guideline), "red"),
)

return not missing_guidelines, \
[fixit.AddLabelAction("guideline:%s" % (guideline))
for guideline in missing_guidelines]
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ProfileDefaultSubsetEqSensitiveSubsetEqExtreme(Base):
supports_fixes = True

@classmethod
def check(cls, labels: MultipleLabels, checker: str) \
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[fixit.FixAction]]:
profiles: Set[str] = set(labels[checker].get("profile", list()))
expected_profiles: Set[str] = set()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
from typing import List, Set, Tuple

from ...checker_labels import MultipleLabels
from ... import fixit
from .base import Base


class ProfileNoAlphaCheckersInProduction(Base):
kind = "profile.no_alpha_checkers_in_production_profiles"
description = """
Ensures that Clang SA checkers in the 'alpha.' (and 'debug.') checker groups
do not belong to a production-grade "profile", e.g., 'default' or 'security'.
""".replace('\n', ' ')
supports_fixes = True

# FIXME(v6.25?): It is planned that we will create a profile specifically
# for Alpha checkers that are not good enough to be possible to lift from
# Alpha status, but not bad enough to be completely unusable, in order to
# suggest ad-hoc use for interested clients.
# These groups **SHOULD** allow Alpha checkers.
profiles_allowing_alphas: Set[str] = {"<placeholder>",
}

@classmethod
def supports_analyser(cls, analyser: str) -> bool:
return analyser == "clangsa"

@classmethod
def check(cls, labels: MultipleLabels, analyser: str, checker: str) \
-> Tuple[bool, List[fixit.FixAction]]:
if not cls.supports_analyser(analyser) \
or not checker.startswith(("alpha.", "debug.")):
return True, []

profiles: Set[str] = set(labels[checker].get("profile", list()))
unexpected_profiles = profiles - cls.profiles_allowing_alphas

return not unexpected_profiles, \
[fixit.RemoveLabelAction("profile:%s" % (profile))
for profile in unexpected_profiles]
3 changes: 2 additions & 1 deletion scripts/labels/invariant_check/tool/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ def verify_invariant(analyser: str,
ok_rules, not_ok_rules = list(), list()

for rule in rules:
rule_status, rule_fixes = rule.check({checker: labels}, checker)
rule_status, rule_fixes = rule.check({checker: labels},
analyser, checker)
if rule_status:
if OutputSettings.report_ok():
log("%s%s/%s: %s %s",
Expand Down
13 changes: 9 additions & 4 deletions scripts/labels/invariant_check/tool/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Statistics(NamedTuple):

Analyser: str
Checkers: int
Invariant: str
Rule: str
OK: Optional[int]
Not_OK: Optional[int]
Fixed: Optional[int]
Expand Down Expand Up @@ -60,19 +60,23 @@ def print_fixes(analyser: str, fixes: Dict[str, List[FixAction]]):
plural(num_fixes, "fix is", "fixes are"))

for checker in sorted(fixes.keys()):
checker_fixes = fixes[checker]
if not checker_fixes:
continue

log(" %s· %s",
emoji(":magnifying_glass_tilted_right: "),
coloured(checker, "cyan"))

for fix in fixes[checker]:
for fix in checker_fixes:
if isinstance(fix, AddLabelAction):
fix_str = "+ %s" % (coloured(fix.new, "green"))
elif isinstance(fix, ModifyLabelAction):
fix_str = "[%s] -> [%s]" % (
coloured(fix.old, "red"),
coloured(fix.new, "green"))
elif isinstance(fix, RemoveLabelAction):
fix_str = "- %s" % (coloured(fix.new, "red"))
fix_str = "- %s" % (coloured(fix.old, "red"))
else:
fix_str = coloured(str(fix), "magenta")

Expand All @@ -98,14 +102,15 @@ def execute(analyser: str,
stats: List[Statistics] = list()
fixes: FixMap = dict()

rules = [rule for rule in rules if rule.supports_analyser(analyser)]
with Pool(max_workers=process_count) as pool:
ok_rules, not_ok_rules, fixing_rules, fixes = \
action.run_check(pool, analyser, rules, labels)

for rule in rules:
stats.append(Statistics(Analyser=analyser,
Checkers=len(labels),
Invariant=rule.kind,
Rule=rule.kind,
OK=ok_rules.get(rule.kind, None),
Not_OK=not_ok_rules.get(rule.kind, None),
Fixed=fixing_rules.get(rule.kind, None),
Expand Down

0 comments on commit 7210e00

Please sign in to comment.