Add 'strict pointer mode' to BlendFile

Add a 'strict pointer mode' to the `BlendFile` class, which is enabled
by default. This allows users of the `BlendFile` class to decide whether
a bad pointer (i.e. one that points to a non-existing datablock) returns
`None` or raises a `SegmentationFault` exception.
This commit is contained in:
Sybren A. Stüvel 2021-07-22 10:28:48 +02:00
parent 803c38dac1
commit 087ff25c76
3 changed files with 45 additions and 7 deletions

View File

@ -106,6 +106,12 @@ class BlendFile:
log = log.getChild("BlendFile") log = log.getChild("BlendFile")
strict_pointer_mode = True
"""Raise exceptions.SegmentationFault when dereferencing an unknown pointer.
Set to False to disable this exception, and to return None instead.
"""
def __init__(self, path: pathlib.Path, mode="rb") -> None: def __init__(self, path: pathlib.Path, mode="rb") -> None:
"""Create a BlendFile instance for the blend file at the path. """Create a BlendFile instance for the blend file at the path.
@ -401,15 +407,21 @@ class BlendFile:
return abspath return abspath
def dereference_pointer(self, address: int) -> "BlendFileBlock": def dereference_pointer(self, address: int) -> typing.Optional["BlendFileBlock"]:
"""Return the pointed-to block, or raise SegmentationFault.""" """Return the pointed-to block, or raise SegmentationFault.
When BlendFile.strict_pointer_mode is False, the exception will not be
thrown, but None will be returned.
"""
try: try:
return self.block_from_addr[address] return self.block_from_addr[address]
except KeyError: except KeyError:
if self.strict_pointer_mode:
raise exceptions.SegmentationFault( raise exceptions.SegmentationFault(
"address does not exist", address "address does not exist", address
) from None ) from None
return None
def struct(self, name: bytes) -> dna.Struct: def struct(self, name: bytes) -> dna.Struct:
index = self.sdna_index_from_id[name] index = self.sdna_index_from_id[name]
@ -755,7 +767,11 @@ class BlendFileBlock:
address = endian.read_pointer(fileobj, ps) address = endian.read_pointer(fileobj, ps)
if address == 0: if address == 0:
continue continue
yield self.bfile.dereference_pointer(address) dereferenced = self.bfile.dereference_pointer(address)
if dereferenced is None:
# This can happen when strict pointer mode is disabled.
continue
yield dereferenced
def iter_fixed_array_of_pointers( def iter_fixed_array_of_pointers(
self, path: dna.FieldPath self, path: dna.FieldPath
@ -786,7 +802,12 @@ class BlendFileBlock:
if not address: if not address:
# Fixed-size arrays contain 0-pointers. # Fixed-size arrays contain 0-pointers.
continue continue
yield self.bfile.dereference_pointer(address)
dereferenced = self.bfile.dereference_pointer(address)
if dereferenced is None:
# This can happen when strict pointer mode is disabled.
continue
yield dereferenced
def __getitem__(self, path: dna.FieldPath): def __getitem__(self, path: dna.FieldPath):
return self.get(path) return self.get(path)

View File

@ -512,6 +512,14 @@ class Packer:
# Find the same block in the newly copied file. # Find the same block in the newly copied file.
block = bfile.dereference_pointer(usage.block.addr_old) block = bfile.dereference_pointer(usage.block.addr_old)
# Pointers can point to a non-existing data block, in which case
# either a SegmentationFault exception is thrown, or None is
# returned, based on the strict pointer mode set on the
# BlendFile class. Since this block was already meant to be
# rewritten, it was found before.
assert block is not None
if usage.path_full_field is None: if usage.path_full_field is None:
dir_field = usage.path_dir_field dir_field = usage.path_dir_field
assert dir_field is not None assert dir_field is not None

View File

@ -217,6 +217,15 @@ class PointerTest(AbstractBlendFileTest):
with self.assertRaises(exceptions.SegmentationFault): with self.assertRaises(exceptions.SegmentationFault):
scene.get_pointer(b"ed") scene.get_pointer(b"ed")
def test_disabled_strict_pointer_mode(self):
scene = self.bf.code_index[b"SC"][0]
ed_ptr = scene.get(b"ed")
del self.bf.block_from_addr[ed_ptr]
self.bf.strict_pointer_mode = False
dereferenced = scene.get_pointer(b"ed")
self.assertIsNone(dereferenced)
def test_abs_offset(self): def test_abs_offset(self):
scene = self.bf.code_index[b"SC"][0] scene = self.bf.code_index[b"SC"][0]
ed = scene.get_pointer(b"ed") ed = scene.get_pointer(b"ed")