Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions fusil/python/write_python_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,24 @@ def _write_script_header_and_imports(self) -> None:
def skip_trivial_type(obj_instance_or_class):
if type(obj_instance_or_class) in TRIVIAL_TYPES:
return True
# The int-as-pointer family, reached as a module ATTRIBUTE rather than as a
# module. --blacklist already excludes _cffi_backend/_rawffi/ctypes by NAME, but
# PyPy's stdlib hands out live cffi objects as attributes -- resource.ffi,
# _ssl.ffi, _sqlite3._ffi, _lzma.ffi, _gdbm.ffi, _curses.ffi, _tkinter.tkffi,
# _sha3._ffi, _posixshmem.ffi and their matching .lib -- so the surface is
# reachable regardless of the module blacklist. ffi.memmove(int, int, n) treats
# its arguments as raw pointers and dies BY CONTRACT on whatever integer it is
# handed, exactly as CPython's ctypes does on the same input, so a fault here is
# never a target defect. Seen in four consecutive PyPy fleets (05, 06, 07, 08).
#
# (Worded without fusil's own crash vocabulary on purpose: help() echoes emitted
# comments via inspect.getcomments, and SelfNoiseVocabularyTests enforces it.)
#
# Matched on the TYPE's defining module, not on the attribute name: an
# OBJECT_BLACKLIST entry for "ffi"/"lib"/"_ffi"/"tkffi" would match those names
# on unrelated targets that legitimately expose them.
if type(obj_instance_or_class).__module__ == "_cffi_backend":
return True
return False
"""),
)
Expand Down
18 changes: 18 additions & 0 deletions tests/python/golden/fakemod_seed1234.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@
def skip_trivial_type(obj_instance_or_class):
if type(obj_instance_or_class) in TRIVIAL_TYPES:
return True
# The int-as-pointer family, reached as a module ATTRIBUTE rather than as a
# module. --blacklist already excludes _cffi_backend/_rawffi/ctypes by NAME, but
# PyPy's stdlib hands out live cffi objects as attributes -- resource.ffi,
# _ssl.ffi, _sqlite3._ffi, _lzma.ffi, _gdbm.ffi, _curses.ffi, _tkinter.tkffi,
# _sha3._ffi, _posixshmem.ffi and their matching .lib -- so the surface is
# reachable regardless of the module blacklist. ffi.memmove(int, int, n) treats
# its arguments as raw pointers and dies BY CONTRACT on whatever integer it is
# handed, exactly as CPython's ctypes does on the same input, so a fault here is
# never a target defect. Seen in four consecutive PyPy fleets (05, 06, 07, 08).
#
# (Worded without fusil's own crash vocabulary on purpose: help() echoes emitted
# comments via inspect.getcomments, and SelfNoiseVocabularyTests enforces it.)
#
# Matched on the TYPE's defining module, not on the attribute name: an
# OBJECT_BLACKLIST entry for "ffi"/"lib"/"_ffi"/"tkffi" would match those names
# on unrelated targets that legitimately expose them.
if type(obj_instance_or_class).__module__ == "_cffi_backend":
return True
return False


Expand Down
63 changes: 63 additions & 0 deletions tests/python/test_golden_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,69 @@ def test_full_script_matches_snapshot(self):
)


class SkipTrivialTypeTests(unittest.TestCase):
"""The emitted ``skip_trivial_type`` must refuse to dive into cffi objects.

``--blacklist`` excludes ``_cffi_backend`` / ``_rawffi`` / ``ctypes`` by MODULE NAME, but
PyPy's stdlib hands out live cffi objects as module ATTRIBUTES -- ``resource.ffi``,
``_ssl.ffi``, ``_sqlite3._ffi``, ``_lzma.ffi``, ``_tkinter.tkffi`` and their matching
``.lib`` -- so the int-as-pointer surface was reachable regardless. Four consecutive PyPy
fleets kept dirs from it.

The function is emitted as SOURCE into every generated script, so testing the snapshot
text alone would not catch a logic error in it. Exec the emitted definition and exercise
it, matching on the type's defining module rather than on the attribute name.
"""

def _emitted_skip_trivial_type(self):
source = generate()
tree = ast.parse(source)
for node in tree.body:
if isinstance(node, ast.FunctionDef) and node.name == "skip_trivial_type":
namespace = {}
# TRIVIAL_TYPES is assigned just above the def in the same emitted block.
for prior in tree.body:
if (
isinstance(prior, ast.Assign)
and getattr(prior.targets[0], "id", "") == "TRIVIAL_TYPES"
):
exec(compile(ast.Module([prior], []), "<emitted>", "exec"), namespace)
exec(compile(ast.Module([node], []), "<emitted>", "exec"), namespace)
return namespace["skip_trivial_type"]
self.fail("skip_trivial_type was not emitted")

def test_cffi_objects_are_skipped_and_ordinary_objects_are_not(self):
skip = self._emitted_skip_trivial_type()

class _FakeFFI:
pass

# cffi types report _cffi_backend as their defining module; that is the signal, not
# the attribute name the object happens to be bound to.
_FakeFFI.__module__ = "_cffi_backend"
self.assertTrue(skip(_FakeFFI()))

class _OrdinaryTarget:
pass

self.assertFalse(skip(_OrdinaryTarget()))

def test_trivial_types_are_still_skipped(self):
skip = self._emitted_skip_trivial_type()
for value in (1, "s", 1.0, True, b"b", (), [], {}, set(), None):
with self.subTest(value=value):
self.assertTrue(skip(value))

def test_an_attribute_merely_NAMED_ffi_is_not_skipped(self):
"""Name-based matching would collide with legitimate targets; this is type-based."""
skip = self._emitted_skip_trivial_type()

class ffi: # noqa: N801 - deliberately named like the cffi attribute
pass

self.assertFalse(skip(ffi()))


class SelfNoiseVocabularyTests(unittest.TestCase):
"""Emitted COMMENTS must not contain fusil's own crash vocabulary.

Expand Down
Loading