mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 09:48:18 +00:00
203 lines
7.4 KiB
Python
203 lines
7.4 KiB
Python
"""Metamorphic-law validation for the abstract evidence domain (task 13.1).
|
|||
|
|
|
||
|
|
Covers the requirements the 7.1 algebra tests and the 10.4/10.5 fixture tests
|
||
|
|
leave as cross-domain laws only: the mixed-obligation rule swept over every
|
||
|
|
per-kind relation pair, judgment preservation under bijective type renaming,
|
||
|
|
and the Req 11.6 ambiguity-monotonicity law (adding ambiguity preserves a
|
||
|
|
judgment or makes it indeterminate -- it never flips unguarded/discharged).
|
||
|
|
Every law runs twice and asserts equal results (deterministic across repeated
|
||
|
|
runs).
|
||
|
|
|
||
|
|
Reqs 11.2-11.7. The join-table, conjunction, witness-aggregation and
|
||
|
|
monotonicity laws of the domain itself are pinned in
|
||
|
|
tests/test_obligation_abstract_domain.py and are not duplicated here.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import itertools
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from openultrasast.semantic.obligations.abstract_domain import (
|
||
|
|
AuthorityImplication,
|
||
|
|
AuthorityRef,
|
||
|
|
Completeness,
|
||
|
|
EvidenceRelation,
|
||
|
|
ResourceRef,
|
||
|
|
WitnessMatch,
|
||
|
|
)
|
||
|
|
from openultrasast.semantic.obligations.cfg_dominance import DominanceEvidence
|
||
|
|
from openultrasast.semantic.obligations.judgment import judgment_for_path
|
||
|
|
from openultrasast.semantic.obligations.operations import Discharge, Operation
|
||
|
|
from openultrasast.semantic.obligations.witness import match_witness
|
||
|
|
|
||
|
|
KINDS = ("path_guard", "ownership_check")
|
||
|
|
RELATIONS = (
|
||
|
|
EvidenceRelation.MUST,
|
||
|
|
EvidenceRelation.MAY,
|
||
|
|
EvidenceRelation.NO,
|
||
|
|
EvidenceRelation.UNKNOWN,
|
||
|
|
)
|
||
|
|
_COMPLETE = Completeness(is_complete=True)
|
||
|
|
|
||
|
|
|
||
|
|
def _operation() -> Operation:
|
||
|
|
return Operation(
|
||
|
|
"src/C.java",
|
||
|
|
4,
|
||
|
|
"handle",
|
||
|
|
"protected_read",
|
||
|
|
"f:op",
|
||
|
|
"book",
|
||
|
|
KINDS,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _pair(relation: EvidenceRelation) -> tuple[WitnessMatch, DominanceEvidence]:
|
||
|
|
match = WitnessMatch(
|
||
|
|
relation=relation,
|
||
|
|
resource_relation=EvidenceRelation.MUST,
|
||
|
|
authority_relation=EvidenceRelation.MUST,
|
||
|
|
evidence=("witness:f:guard",),
|
||
|
|
)
|
||
|
|
return match, DominanceEvidence(relation=EvidenceRelation.MUST, guard_effect="throws")
|
||
|
|
|
||
|
|
|
||
|
|
def _matches_by_kind(
|
||
|
|
path_guard_relation: EvidenceRelation, ownership_relation: EvidenceRelation
|
||
|
|
) -> dict[str, tuple[tuple[WitnessMatch, DominanceEvidence], ...]]:
|
||
|
|
return {
|
||
|
|
"path_guard": (_pair(path_guard_relation),),
|
||
|
|
"ownership_check": (_pair(ownership_relation),),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _expected_judgment(path_guard_relation: EvidenceRelation, ownership_relation: EvidenceRelation) -> str:
|
||
|
|
relations = (path_guard_relation, ownership_relation)
|
||
|
|
if any(relation not in (EvidenceRelation.MUST, EvidenceRelation.NO) for relation in relations):
|
||
|
|
return "indeterminate"
|
||
|
|
if EvidenceRelation.NO in relations:
|
||
|
|
missing = sorted(kind for kind, relation in zip(KINDS, relations, strict=True) if relation is EvidenceRelation.NO)
|
||
|
|
return "unguarded:" + "+".join(missing)
|
||
|
|
return "discharged"
|
||
|
|
|
||
|
|
|
||
|
|
def _judgment_name(judgment: object) -> str:
|
||
|
|
value = getattr(judgment, "value", None)
|
||
|
|
if isinstance(value, str):
|
||
|
|
return value
|
||
|
|
return getattr(judgment, "name", str(judgment))
|
||
|
|
|
||
|
|
|
||
|
|
def test_mixed_obligation_rule_sweeps_every_relation_pair() -> None:
|
||
|
|
"""Reqs 11.2-11.4: the mixed-obligation rule holds for every per-kind pair."""
|
||
|
|
for path_guard_relation, ownership_relation in itertools.product(RELATIONS, RELATIONS):
|
||
|
|
judgment = judgment_for_path(
|
||
|
|
_operation(),
|
||
|
|
_matches_by_kind(path_guard_relation, ownership_relation),
|
||
|
|
_COMPLETE,
|
||
|
|
)
|
||
|
|
expected = _expected_judgment(path_guard_relation, ownership_relation)
|
||
|
|
name = _judgment_name(judgment.judgment)
|
||
|
|
if expected.startswith("unguarded:"):
|
||
|
|
assert name == "unguarded", (path_guard_relation, ownership_relation, expected, name)
|
||
|
|
assert set(judgment.missing) == set(expected.split(":", 1)[1].split("+"))
|
||
|
|
else:
|
||
|
|
assert name == expected, (path_guard_relation, ownership_relation, expected, name)
|
||
|
|
|
||
|
|
|
||
|
|
def test_type_renaming_preserves_judgment_and_matching() -> None:
|
||
|
|
"""Req 11.7: bijective renames of resources, authorities and fact ids change nothing observable."""
|
||
|
|
witness = Discharge(
|
||
|
|
"src/C.java",
|
||
|
|
3,
|
||
|
|
"handle",
|
||
|
|
"path_guard",
|
||
|
|
"java:guard",
|
||
|
|
"call",
|
||
|
|
"statement",
|
||
|
|
"",
|
||
|
|
resource=ResourceRef(kind="exact", values=("book",)),
|
||
|
|
authority=AuthorityRef(values=("admin",)),
|
||
|
|
)
|
||
|
|
renamed_witness = Discharge(
|
||
|
|
"src/C.java",
|
||
|
|
3,
|
||
|
|
"handle",
|
||
|
|
"path_guard",
|
||
|
|
"renamed:guard",
|
||
|
|
"call",
|
||
|
|
"statement",
|
||
|
|
"",
|
||
|
|
resource=ResourceRef(kind="exact", values=("tome",)),
|
||
|
|
authority=AuthorityRef(values=("root",)),
|
||
|
|
)
|
||
|
|
operation = _operation()
|
||
|
|
renamed_operation = Operation(
|
||
|
|
"src/C.java",
|
||
|
|
4,
|
||
|
|
"handle",
|
||
|
|
"protected_read",
|
||
|
|
"renamed:op",
|
||
|
|
"tome",
|
||
|
|
KINDS,
|
||
|
|
)
|
||
|
|
implication = AuthorityImplication(id="impl:1", authority="admin", implies="supervisor")
|
||
|
|
renamed_implication = AuthorityImplication(id="impl:1", authority="root", implies="manager")
|
||
|
|
required = AuthorityRef(values=("supervisor",))
|
||
|
|
renamed_required = AuthorityRef(values=("manager",))
|
||
|
|
|
||
|
|
base = match_witness(witness, operation, None, required_authority=required, implications=(implication,))
|
||
|
|
renamed = match_witness(
|
||
|
|
renamed_witness,
|
||
|
|
renamed_operation,
|
||
|
|
None,
|
||
|
|
required_authority=renamed_required,
|
||
|
|
implications=(renamed_implication,),
|
||
|
|
)
|
||
|
|
assert base.relation is renamed.relation is EvidenceRelation.MUST
|
||
|
|
assert base.resource_relation is renamed.resource_relation
|
||
|
|
assert base.authority_relation is renamed.authority_relation
|
||
|
|
|
||
|
|
|
||
|
|
def test_added_ambiguity_never_flips_discharged_to_unguarded() -> None:
|
||
|
|
"""Req 11.6: degrading any dimension of a discharged pair keeps it discharged or makes it indeterminate."""
|
||
|
|
degraded_relations = (EvidenceRelation.MAY, EvidenceRelation.UNKNOWN)
|
||
|
|
for degraded in degraded_relations:
|
||
|
|
for kind_index in range(len(KINDS)):
|
||
|
|
relations = [EvidenceRelation.MUST, EvidenceRelation.MUST]
|
||
|
|
relations[kind_index] = degraded
|
||
|
|
judgment = judgment_for_path(
|
||
|
|
_operation(),
|
||
|
|
_matches_by_kind(relations[0], relations[1]),
|
||
|
|
_COMPLETE,
|
||
|
|
)
|
||
|
|
assert _judgment_name(judgment.judgment) in ("discharged", "indeterminate"), (degraded, kind_index)
|
||
|
|
|
||
|
|
|
||
|
|
def test_added_ambiguity_never_flips_unguarded_to_discharged() -> None:
|
||
|
|
"""Req 11.6: degrading a governed kind of an unguarded pair keeps it unguarded or makes it indeterminate."""
|
||
|
|
degraded_relations = (EvidenceRelation.MAY, EvidenceRelation.UNKNOWN)
|
||
|
|
for degraded in degraded_relations:
|
||
|
|
judgment = judgment_for_path(
|
||
|
|
_operation(),
|
||
|
|
_matches_by_kind(EvidenceRelation.NO, degraded),
|
||
|
|
_COMPLETE,
|
||
|
|
)
|
||
|
|
assert _judgment_name(judgment.judgment) in ("unguarded", "indeterminate"), degraded
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
("path_guard_relation", "ownership_relation"),
|
||
|
|
list(itertools.product(RELATIONS, RELATIONS)),
|
||
|
|
)
|
||
|
|
def test_laws_hold_deterministically_across_repeated_runs(
|
||
|
|
path_guard_relation: EvidenceRelation, ownership_relation: EvidenceRelation
|
||
|
|
) -> None:
|
||
|
|
"""Req 11.7: two evaluations of the same law produce identical results."""
|
||
|
|
matches = _matches_by_kind(path_guard_relation, ownership_relation)
|
||
|
|
first = judgment_for_path(_operation(), matches, _COMPLETE)
|
||
|
|
second = judgment_for_path(_operation(), matches, _COMPLETE)
|
||
|
|
assert first == second
|