diff --git a/cle/memory.py b/cle/memory.py index e2de3f13..22ed90e8 100644 --- a/cle/memory.py +++ b/cle/memory.py @@ -724,6 +724,13 @@ def __getitem__(self, k) -> int: def __setitem__(self, k, v): raise NotImplementedError("ClemoryReadOnlyView does not support item assignment") + def __contains__(self, k) -> bool: + try: + self[k] + except KeyError: + return False + return True + def load(self, addr: int, n: int) -> bytes: """ Read up to `n` bytes at address `addr` in memory and return a bytes object. diff --git a/tests/test_clemory.py b/tests/test_clemory.py index 432dfc16..287b4054 100644 --- a/tests/test_clemory.py +++ b/tests/test_clemory.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import sys import timeit import unittest @@ -8,6 +9,8 @@ import cle +TEST_BASE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "binaries", "tests") + @unittest.skipIf(sys.platform == "emscripten", "runtime CFFI compilation is unavailable in Pyodide") def test_cclemory(): # pylint: disable=no-member @@ -74,6 +77,23 @@ def test_clemory(): assert clemory.load(10, 25) == b"A" * 20 +def test_clemory_read_only_view_contains(): + loader = cle.Loader(os.path.join(TEST_BASE, "x86_64", "fauxware"), auto_load_libs=False) + loader.gen_ro_memview() + view = loader.memory_ro_view + assert view is not None + + entry = loader.main_object.entry + assert entry in view + assert entry - 0x10000 not in view + + # The view answers the same as the clemory it was flattened from, including in the gaps + # between backers. + for start, backer in loader.memory.backers(): + for addr in (start - 1, start, start + len(backer) - 1, start + len(backer)): + assert (addr in view) == (addr in loader.memory) + + def performance_clemory_contains(): # With the consecutive optimization: # 5.72 sec