| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from difflib import SequenceMatcher |
| from typing import List, Optional, Sequence, Tuple, FrozenSet, Set, Dict, NamedTuple |
| |
| from difl.ir import Library, Struct, StructMember, Type, Table, Union, Protocol, Enum |
| from difl.changes import * |
| from difl.intersection import intersect_changes |
| from difl.comparator import Comparator |
| |
| |
| class StructLayoutDifferences(NamedTuple): |
| unmoved: List[str] |
| moved: List[str] |
| resized: List[str] |
| renamed: List[Tuple[str, str]] |
| added: List[str] |
| removed: List[str] |
| split: List[Tuple[str, List[str]]] |
| joined: List[Tuple[List[str], str]] |
| |
| |
| def _member_bytes(names: Set[str], members: Dict[str, StructMember] |
| ) -> Dict[str, FrozenSet[int]]: |
| ''' |
| Takes a set of member names and a dict of named struct members and |
| returns a dict of member names to a set of the byte offsets that the |
| members occupy. |
| ''' |
| member_bytes: Dict[str, FrozenSet[int]] = {} |
| for name in names: |
| member = members[name] |
| member_bytes[name] = frozenset( |
| range(member.offset, member.offset + member.size)) |
| return member_bytes |
| |
| |
| def _overlapping(outer: FrozenSet[int], |
| inners: Dict[str, FrozenSet[int]]) -> Optional[List[str]]: |
| ''' |
| Takes an outer set and a dict of named disjunct inner sets. |
| If there are one or more inner sets whose union is equal to the outer set, return their names. |
| ''' |
| intersecting: List[str] = [] |
| intersection: Set[int] = set() |
| for name in inners.keys(): |
| if outer.issuperset(inners[name]): |
| intersecting.append(name) |
| intersection.update(inners[name]) |
| if intersection == outer: |
| return intersecting |
| else: |
| return None |
| |
| |
| def _all_overlapping( |
| outers: Dict[str, FrozenSet[int]], |
| inners: Dict[str, FrozenSet[int]]) -> List[Tuple[str, List[str]]]: |
| ''' |
| For every outer set named in look for some inner sets whose union equals the outer set. |
| Remove matched sets from the supplied dictionaries and return a list tuples of the matches. |
| ''' |
| overlaps: List[Tuple[str, List[str]]] = [] |
| for name in frozenset(outers.keys()): |
| overlapping = _overlapping(outers[name], inners) |
| if overlapping is None: continue |
| overlaps.append((name, overlapping)) |
| outers.pop(name) |
| for o in overlapping: |
| inners.pop(o) |
| return overlaps |
| |
| |
| def compare_struct_layout(before: Struct, |
| after: Struct) -> StructLayoutDifferences: |
| before_members = {m.name: m for m in before.members} |
| after_members = {m.name: m for m in after.members} |
| before_names = set(before_members.keys()) |
| after_names = set(after_members.keys()) |
| common_names = before_names.intersection(after_names) |
| only_before_names = before_names - common_names |
| only_after_names = after_names - common_names |
| |
| # categorize struct members that exist before and after into moved and unmoved |
| unmoved: List[str] = [] |
| moved: List[str] = [] |
| resized: List[str] = [] |
| for name in common_names: |
| before_member: StructMember = before_members[name] |
| after_member: StructMember = after_members[name] |
| if before_member.offset != after_member.offset: |
| moved.append(name) |
| elif before_member.size != after_member.size: |
| resized.append(name) |
| else: |
| unmoved.append(name) |
| |
| # look for members that have been renamed |
| renamed: List[Tuple[str, str]] = [] |
| for name in frozenset(only_before_names): |
| before_member = before_members[name] |
| # look for a member whose location matches |
| for after_member in [after_members[n] for n in only_after_names]: |
| if before_member.offset == after_member.offset and \ |
| before_member.size == after_member.size: |
| renamed.append((before_member.name, after_member.name)) |
| # remove these members from further consideration |
| only_before_names.remove(before_member.name) |
| only_after_names.remove(after_member.name) |
| |
| # TODO: split and join logic should probably be limited to integer primitives and enums |
| |
| before_bytes = _member_bytes(only_before_names, before_members) |
| after_bytes = _member_bytes(only_after_names, after_members) |
| |
| # look for before members whose space is wholy overlapping with some after members |
| split: List[Tuple[str, List[str]]] = _all_overlapping( |
| before_bytes, after_bytes) |
| # look for after members whose space is wholy overlapping with some before members |
| joined: List[Tuple[List[str], str]] = [ |
| (y, x) for x, y in _all_overlapping(after_bytes, before_bytes) |
| ] |
| |
| return StructLayoutDifferences( |
| unmoved=unmoved, |
| moved=moved, |
| resized=resized, |
| renamed=renamed, |
| split=split, |
| joined=joined, |
| removed=list(before_bytes.keys()), |
| added=list(after_bytes.keys())) |
| |
| |
| def struct_changes(before: Struct, after: Struct, |
| comparator: Comparator) -> List[Change]: |
| changes: List[Change] = [] |
| |
| if before.size != after.size: |
| changes.append(StructSizeChanged(before, after)) |
| |
| layout_diff = compare_struct_layout(before, after) |
| |
| before_members: Dict[str, StructMember] = { |
| m.name: m |
| for m in before.members |
| } |
| after_members: Dict[str, StructMember] = {m.name: m for m in after.members} |
| |
| for name in layout_diff.unmoved: |
| before_member = before_members[name] |
| after_member = after_members[name] |
| if not comparator.constraints_match(before_member.type, |
| after_member.type): |
| changes.append( |
| StructMemberTypeChanged( |
| before_member, after_member, not comparator.shapes_match( |
| before_member.type, after_member.type))) |
| |
| for name in layout_diff.moved: |
| before_member = before_members[name] |
| after_member = after_members[name] |
| changes.append(StructMemberMoved(before_member, after_member)) |
| if not comparator.constraints_match(before_member.type, |
| after_member.type): |
| changes.append( |
| StructMemberTypeChanged( |
| before_member, after_member, not comparator.shapes_match( |
| before_member.type, after_member.type))) |
| |
| for name in layout_diff.resized: |
| before_member = before_members[name] |
| after_member = after_members[name] |
| changes.append(StructMemberSizeChanged(before_member, after_member)) |
| assert not comparator.shapes_match(before_member.type, |
| after_member.type) |
| changes.append( |
| StructMemberTypeChanged(before_member, after_member, True)) |
| |
| for before_name, after_name in layout_diff.renamed: |
| changes.append( |
| StructMemberRenamed(before_members[before_name], |
| after_members[after_name])) |
| |
| for name in layout_diff.added: |
| changes.append(StructMemberAdded(before, after_members[name])) |
| |
| for name in layout_diff.removed: |
| changes.append(StructMemberRemoved(before_members[name], after)) |
| |
| for before_name, after_names in layout_diff.split: |
| changes.append( |
| StructMemberSplit(before_members[before_name], |
| [after_members[n] for n in after_names])) |
| |
| for before_names, after_name in layout_diff.joined: |
| changes.append( |
| StructMemberJoined([before_members[n] for n in before_names], |
| after_members[after_name])) |
| |
| return changes |