Skip to content

Return False from ClemoryView.__contains__ instead of raising - #819

Open
zardus wants to merge 1 commit into
masterfrom
feature/clemoryview-contains
Open

zardus wants to merge 1 commit into
masterfrom
feature/clemoryview-contains

Conversation

@zardus

@zardus zardus commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

addr in view raises instead of answering, where addr in clemory answers:

>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBBCCCCDDDD')
>>> view = cle.ClemoryView(m, 0x1000, 0x1010)
>>> 0x10 in view
KeyError: 16
>>> 0x1010 in m
False

So a caller cannot use a ClemoryView where it uses a Clemory without wrapping every
membership test in a try/except KeyError.

Root cause

ClemoryView.__contains__ raises for an address outside the view rather than reporting that
it is not in it:

def __contains__(self, k):
    if not self._offset <= k < self._endoffset:
        raise KeyError(k)
    return k + self._rebase in self._backer

The range check is right; only what it does with the answer is wrong. Clemory.__contains__
returns False in the same situation, and in is defined to give a boolean.

Fix

Return False. The rest of the method is unchanged, and an address inside the view is still
answered by asking the parent.

Nothing in cle at 0e77ade3, angr at 87411a71 or angr-management at aa843e5c
constructs a ClemoryView or subclasses one, so this has no in-tree caller; the class is
exported from cle and the method is wrong for anyone who does.

Testing

tests/test_clemory.py gains test_clemory_view_contains: both edges of the window, one past
each end, and the same four addresses shifted for a view built with a non-zero offset, which
pins that the answer is about the view's own address space. It fails on master with
KeyError: 16.

Validation: #819 (comment)

session: sharpen

__contains__ raised KeyError for an address outside the view, so `addr in view`
could not be used the way `addr in clemory` can: Clemory.__contains__ answers
the question for any address, and a caller has to guard the view's version with
a try/except to get the same answer.
@zardus

zardus commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 05103ef73495ce7622c6f6d638282d4aee6b8b1b against baseline
0e77ade3c39a3cee05f65051e57955675e1ac21b, with angr/binaries at
003e82a2bfa641530924055695b36cec8af483ab.

  • Regression: the head's tests/test_clemory.py run against the baseline's cle gives
    FAILED test_clemory_view_contains - KeyError: 16, 1 failed 3 passed. The same file at the
    head gives 4 passed
  • Focused: python -m pytest tests/test_clemory.py -q — baseline 3 passed, head 4 passed
  • Full suite: python -m pytest tests -q — baseline 261 passed, 9 skipped; head 262 passed,
    9 skipped. No failures on either arm
  • Lint/type: the merge-base comparison the hosted Lint and Typecheck jobs make, against
    0e77ade3 — pylint cle/memory.py 10.00 -> 10.00, tests/test_clemory.py 5.12 -> 5.79;
    pyright errors cle/memory.py 0 -> 0, tests/test_clemory.py 9 -> 9. The
    # type: ignore[arg-type] on the one new cle.Clemory(None, root=True) line is
    load-bearing: without it the count is 10
  • Hooks: pre-commit run --all-files exit 0, all 24 hooks pass or skip, nothing rewritten
  • Guards: check-test-inputs.py exit 0; check-stale-pins.py against 0e77ade3 exit 0, and
    structurally null here — the diff touches no pyproject.toml

No corpus comparison is offered. No loader path constructs a ClemoryView, so one would be a
guaranteed null: git grep -n ClemoryView at cle 0e77ade3, angr
87411a719c96ddc3f3954590b1833b1789e19697 and angr-management
aa843e5c10645ba361620768d59642a460805e80 finds no construction and no subclass.

Workspace gate: not run. What ran instead is the scoped equivalent above: cle's own suite in an isolated
worktree, the merge-base lint and type comparison, and the full hook set. The angr,
angr-management, pyvex, archinfo, pypcode and native Rust suites did not run; hosted CI covers
them.

Caveats: git merge-tree puts this in conflict with 718, 721, 788 and 816, and clean against
809 — the five open pull requests touching cle/memory.py when this was measured. Every
conflict is in tests/test_clemory.py only, because each appends tests to the same file, and
cle/memory.py itself auto-merges in all five. Whichever merges first, the rest rebase.

@zardus

zardus commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Membership through a ClemoryView, before and after this change. Same script on both arms.

Before — in raises for anything outside the view, where the parent answers:

cle master 0e77ade
>>> import archinfo, cle
>>> arch = archinfo.arch_from_id('x86_64')
>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBBCCCCDDDD')
>>> view = cle.ClemoryView(m, 0x1000, 0x1010)
>>> 0 in view
True
>>> 0xf in view
True
>>> 0x10 in view
KeyError: 16
>>> -1 in view
KeyError: -1
>>> 0x1010 in m
False

After — in answers, in the view's own address space:

with this change
>>> import archinfo, cle
>>> arch = archinfo.arch_from_id('x86_64')
>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBBCCCCDDDD')
>>> view = cle.ClemoryView(m, 0x1000, 0x1010)
>>> 0 in view
True
>>> 0xf in view
True
>>> 0x10 in view
False
>>> -1 in view
False
>>> 0x1010 in m
False

@angr-bot

angr-bot commented Sep 6, 2026

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_819

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants