Skip to content

Commit

Permalink
Prevent assertion error on messages with unreachable fields
Browse files Browse the repository at this point in the history
Ref. #1033
  • Loading branch information
treiher committed May 17, 2022
1 parent 6e89dbd commit 0ac4d5f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 10 deletions.
30 changes: 20 additions & 10 deletions rflx/model/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class MessageState(Base):
field_types: Mapping[Field, mty.Type] = {}
definite_predecessors: Optional[Mapping[Field, Tuple[Field, ...]]] = None
path_condition: Optional[Mapping[Field, expr.Expr]] = None
has_unreachable = False


@invariant(lambda self: valid_message_field_types(self))
Expand All @@ -128,7 +129,6 @@ def __init__(
self.structure = sorted(structure)

self.__types = types
self.__has_unreachable = False
self.__paths_cache: Dict[Field, Set[Tuple[Link, ...]]] = {}
self._checksums = checksums or {}

Expand All @@ -145,9 +145,9 @@ def __init__(

try:
if not state and (structure or types):
self.__validate()
self._state.has_unreachable = self.__validate()
self.__normalize()
fields = self.__compute_topological_sorting()
fields = self.__compute_topological_sorting(self._state.has_unreachable)
if fields:
self._state.field_types = {f: self.__types[f] for f in fields}
self._state.parameter_types = {
Expand All @@ -159,7 +159,11 @@ def __init__(
self._byte_order = {f: byte_order for f in self.fields}
else:
assert all(f in byte_order for f in self.fields)
assert all(f in self.fields for f in byte_order)
assert (
all(f in self.fields for f in byte_order)
if not self._state.has_unreachable
else True
)
self._byte_order = byte_order
except RecordFluxError:
pass
Expand Down Expand Up @@ -466,7 +470,7 @@ def message_constraints(cls) -> List[expr.Expr]:
expr.Equal(expr.Mod(expr.Size("Message"), expr.Number(8)), expr.Number(0)),
]

def __validate(self) -> None:
def __validate(self) -> bool:
type_fields = {*self.__types.keys(), INITIAL, FINAL}
structure_fields = {l.source for l in self.structure} | {l.target for l in self.structure}

Expand All @@ -476,9 +480,11 @@ def __validate(self) -> None:

self.error.propagate()

self._validate_structure(structure_fields)
has_unreachable = self._validate_structure(structure_fields)
self._validate_link_aspects()

return has_unreachable

def _validate_types(self, type_fields: Set[Field], structure_fields: Set[Field]) -> None:
parameters = self.__types.keys() - structure_fields

Expand Down Expand Up @@ -579,13 +585,15 @@ def _validate_names(self, type_fields: Set[Field]) -> None:
],
)

def _validate_structure(self, structure_fields: Set[Field]) -> None:
def _validate_structure(self, structure_fields: Set[Field]) -> bool:
has_unreachable = False

for f in structure_fields:
for l in self.structure:
if f in (INITIAL, l.target):
break
else:
self.__has_unreachable = True
has_unreachable = True
self.error.extend(
[
(
Expand Down Expand Up @@ -624,6 +632,8 @@ def _validate_structure(self, structure_fields: Set[Field]) -> None:
]
)

return has_unreachable

def _validate_link_aspects(self) -> None:
for link in self.structure:
exponentiations = itertools.chain.from_iterable(
Expand Down Expand Up @@ -734,7 +744,7 @@ def qualify_enum_literals(expression: expr.Expr) -> expr.Expr:
location=link.location,
)

def __compute_topological_sorting(self) -> Optional[Tuple[Field, ...]]:
def __compute_topological_sorting(self, has_unreachable: bool) -> Optional[Tuple[Field, ...]]:
"""Return fields topologically sorted (Kahn's algorithm)."""
result: Tuple[Field, ...] = ()
fields = [INITIAL]
Expand All @@ -746,7 +756,7 @@ def __compute_topological_sorting(self) -> Optional[Tuple[Field, ...]]:
visited.add(e)
if set(self.incoming(e.target)) <= visited:
fields.append(e.target)
if not self.__has_unreachable and set(self.structure) - visited:
if not has_unreachable and set(self.structure) - visited:
self.error.extend(
[
(
Expand Down
28 changes: 28 additions & 0 deletions tests/integration/specification_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,34 @@ def test_invalid_message_with_field_after_field_with_implicit_size() -> None:
)


def test_invalid_message_with_unreachable_field_after_merging() -> None:
assert_error_string(
"""
package Test is
type T is range 0 .. 3 with Size => 8;
type I is
message
A : T;
end message;
type O is
message
C : I
then null
if C_A /= 4
then D
if C_A = 4;
D : T;
end message;
end Test;
""",
r'^<stdin>:18:21: model: error: unreachable field "D" in "Test::O"$',
)


def test_dependency_order() -> None:
p = parser.Parser()
p.parse(Path(f"{SPEC_DIR}/in_p1.rflx"))
Expand Down

0 comments on commit 0ac4d5f

Please sign in to comment.