mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
158 lines
4.9 KiB
Python
158 lines
4.9 KiB
Python
"""Task 10.5: aggregate multiple path judgments in the opt-in abstract mode.
|
|
|
|
Req 3.10 chains stay independent; Reqs 4.1-4.5 findings only for unguarded;
|
|
Req 11.5: unguarded if any complete path is unguarded, discharged only for a
|
|
complete all-discharged enumeration, indeterminate otherwise.
|
|
"""
|
|
|
|
from openultrasast.semantic.obligations.abstract_domain import (
|
|
Completeness,
|
|
PairJudgment,
|
|
PathJudgment,
|
|
WitnessMatch,
|
|
)
|
|
from openultrasast.semantic.obligations.judgment import aggregate_judgments
|
|
|
|
|
|
def _match(evidence: str) -> WitnessMatch:
|
|
from openultrasast.semantic.obligations.abstract_domain import EvidenceRelation
|
|
|
|
return WitnessMatch(
|
|
relation=EvidenceRelation.MUST,
|
|
resource_relation=EvidenceRelation.MUST,
|
|
authority_relation=EvidenceRelation.MUST,
|
|
evidence=(evidence,),
|
|
)
|
|
|
|
|
|
def _judgment(
|
|
judgment: PairJudgment,
|
|
*,
|
|
missing: tuple[str, ...] = (),
|
|
is_complete: bool = True,
|
|
reasons: tuple[str, ...] = (),
|
|
evidence: tuple[str, ...] = (),
|
|
) -> PathJudgment:
|
|
completeness = Completeness(is_complete=is_complete, reasons=reasons)
|
|
return PathJudgment(
|
|
judgment=judgment,
|
|
missing=missing,
|
|
completeness=completeness,
|
|
matches=tuple(_match(item) for item in evidence),
|
|
)
|
|
|
|
|
|
def test_any_complete_unguarded_path_makes_the_aggregate_unguarded() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(PairJudgment.UNGUARDED, missing=("path_guard",)),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.UNGUARDED
|
|
assert aggregate.missing == ("path_guard",)
|
|
|
|
|
|
def test_unguarded_on_an_incomplete_path_does_not_decide() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(
|
|
PairJudgment.UNGUARDED,
|
|
missing=("path_guard",),
|
|
is_complete=False,
|
|
reasons=("resolution:no_candidate",),
|
|
),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.INDETERMINATE
|
|
|
|
|
|
def test_complete_all_discharged_enumeration_is_discharged() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.DISCHARGED
|
|
assert aggregate.missing == ()
|
|
|
|
|
|
def test_discharged_with_one_incomplete_path_is_indeterminate() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(
|
|
PairJudgment.DISCHARGED,
|
|
is_complete=False,
|
|
reasons=("summaries:depth_cutoff",),
|
|
),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.INDETERMINATE
|
|
|
|
|
|
def test_discharged_and_indeterminate_mix_is_indeterminate() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(PairJudgment.INDETERMINATE),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.INDETERMINATE
|
|
|
|
|
|
def test_empty_enumeration_is_indeterminate() -> None:
|
|
aggregate = aggregate_judgments(())
|
|
assert aggregate.judgment is PairJudgment.INDETERMINATE
|
|
assert aggregate.missing == ()
|
|
|
|
|
|
def test_missing_kinds_union_across_complete_unguarded_paths() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.UNGUARDED, missing=("path_guard",)),
|
|
_judgment(
|
|
PairJudgment.UNGUARDED,
|
|
missing=("ownership_check", "path_guard"),
|
|
),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.UNGUARDED
|
|
assert aggregate.missing == ("ownership_check", "path_guard")
|
|
|
|
|
|
def test_result_completeness_joins_input_reasons() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
_judgment(
|
|
PairJudgment.INDETERMINATE,
|
|
is_complete=False,
|
|
reasons=("summaries:depth_cutoff",),
|
|
),
|
|
)
|
|
)
|
|
assert aggregate.judgment is PairJudgment.INDETERMINATE
|
|
assert aggregate.completeness.is_complete is False
|
|
assert "summaries:depth_cutoff" in aggregate.completeness.reasons
|
|
|
|
|
|
def test_matches_from_every_path_are_carried_deduplicated() -> None:
|
|
aggregate = aggregate_judgments(
|
|
(
|
|
_judgment(PairJudgment.UNGUARDED, missing=("path_guard",), evidence=("w:1",)),
|
|
_judgment(PairJudgment.DISCHARGED, evidence=("w:1", "w:2")),
|
|
)
|
|
)
|
|
assert tuple(match.evidence for match in aggregate.matches) == (("w:1",), ("w:2",))
|
|
|
|
|
|
def test_aggregation_is_deterministic() -> None:
|
|
judgments = (
|
|
_judgment(PairJudgment.UNGUARDED, missing=("path_guard",)),
|
|
_judgment(PairJudgment.DISCHARGED),
|
|
)
|
|
assert aggregate_judgments(judgments) == aggregate_judgments(judgments)
|