Better failure mode for sstruct (#3500)

* Check format fits and give sensible error if not

* sstruct.getformat now returns dictionaries, merge them
This commit is contained in:
Simon Cozens 2024-05-07 14:01:46 +01:00 committed by GitHub
parent b88a71bed8
commit 9acbd12637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 19 additions and 8 deletions

View File

@ -64,7 +64,10 @@ def pack(fmt, obj):
elements = []
if not isinstance(obj, dict):
obj = obj.__dict__
for name in names:
string_index = formatstring
if formatstring.startswith(">"):
string_index = formatstring[1:]
for ix, name in enumerate(names.keys()):
value = obj[name]
if name in fixes:
# fixed point conversion
@ -72,6 +75,13 @@ def pack(fmt, obj):
elif isinstance(value, str):
value = tobytes(value)
elements.append(value)
# Check it fits
try:
struct.pack(names[name], value)
except Exception as e:
raise ValueError(
"Value %s does not fit in format %s for %s" % (value, names[name], name)
) from e
data = struct.pack(*(formatstring,) + tuple(elements))
return data
@ -87,7 +97,7 @@ def unpack(fmt, data, obj=None):
d = obj.__dict__
elements = struct.unpack(formatstring, data)
for i in range(len(names)):
name = names[i]
name = list(names.keys())[i]
value = elements[i]
if name in fixes:
# fixed point conversion
@ -141,7 +151,7 @@ def getformat(fmt, keep_pad_byte=False):
except KeyError:
lines = re.split("[\n;]", fmt)
formatstring = ""
names = []
names = {}
fixes = {}
for line in lines:
if _emptyRE.match(line):
@ -158,7 +168,7 @@ def getformat(fmt, keep_pad_byte=False):
name = m.group(1)
formatchar = m.group(2)
if keep_pad_byte or formatchar != "x":
names.append(name)
names[name] = formatchar
if m.group(3):
# fixed point
before = int(m.group(3))
@ -167,9 +177,10 @@ def getformat(fmt, keep_pad_byte=False):
if bits not in [8, 16, 32]:
raise Error("fixed point must be 8, 16 or 32 bits long")
formatchar = _fixedpointmappings[bits]
names[name] = formatchar
assert m.group(5) == "F"
fixes[name] = after
formatstring = formatstring + formatchar
formatstring += formatchar
_formatcache[fmt] = formatstring, names, fixes
return formatstring, names, fixes

View File

@ -298,9 +298,9 @@ class BitmapSizeTable(object):
# cares about in terms of XML creation.
def _getXMLMetricNames(self):
dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
dataNames = dataNames + sstruct.getformat(bitmapSizeTableFormatPart2)[1]
dataNames = {**dataNames, **sstruct.getformat(bitmapSizeTableFormatPart2)[1]}
# Skip the first 3 data names because they are byte offsets and counts.
return dataNames[3:]
return list(dataNames.keys())[3:]
def toXML(self, writer, ttFont):
writer.begintag("bitmapSizeTable")

View File

@ -127,7 +127,7 @@ class table__m_a_x_p(DefaultTable.DefaultTable):
formatstring, names, fixes = sstruct.getformat(maxpFormat_0_5)
if self.tableVersion != 0x00005000:
formatstring, names_1_0, fixes = sstruct.getformat(maxpFormat_1_0_add)
names = names + names_1_0
names = {**names, **names_1_0}
for name in names:
value = getattr(self, name)
if name == "tableVersion":