From f9b927cdbb3f9c943d369d865d28d7fcfc69f167 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 30 Aug 2021 10:35:56 +0100 Subject: [PATCH 1/2] Fix chained contextual builder overflow --- Lib/fontTools/otlLib/builder.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index bfb9d41f8..64cbc5e81 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -377,6 +377,12 @@ class ChainContextualBuilder(LookupBuilder): candidates = [None, None, None, []] for rule in ruleset.rules: candidates[3].append(self.buildFormat3Subtable(rule, chaining)) + # Check we can build it + try: + self.getCompiledSize_(candidates[3]) + except Exception as e: + log.warning("Contextual format 3 at %s overflowed" % str(self.location)) + candidates[3] = None # Can we express the whole ruleset as a format 2 subtable? classdefs = ruleset.format2ClassDefs() @@ -384,11 +390,26 @@ class ChainContextualBuilder(LookupBuilder): candidates[2] = [ self.buildFormat2Subtable(ruleset, classdefs, chaining) ] + # Check we can build it + try: + self.getCompiledSize_(candidates[2]) + except Exception as e: + log.warning("Contextual format 2 at %s overflowed (%s)" % (str(self.location), e)) + candidates[2] = None if not ruleset.hasAnyGlyphClasses: candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)] + # Check we can build it + try: + self.getCompiledSize_(candidates[1]) + except Exception as e: + log.warning("Contextual format 1 at %s overflowed" % str(self.location)) + candidates[1] = None candidates = [x for x in candidates if x is not None] + if not candidates: + raise OpenTypeLibError("All candidates overflowed", self.location) + winner = min(candidates, key=self.getCompiledSize_) subtables.extend(winner) From f53a75fb1dff3407b1366a3d201389d52954933f Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 15 Sep 2021 11:08:50 +0100 Subject: [PATCH 2/2] Check for overflows in a loop --- Lib/fontTools/otlLib/builder.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index 64cbc5e81..153e20e6f 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -377,12 +377,6 @@ class ChainContextualBuilder(LookupBuilder): candidates = [None, None, None, []] for rule in ruleset.rules: candidates[3].append(self.buildFormat3Subtable(rule, chaining)) - # Check we can build it - try: - self.getCompiledSize_(candidates[3]) - except Exception as e: - log.warning("Contextual format 3 at %s overflowed" % str(self.location)) - candidates[3] = None # Can we express the whole ruleset as a format 2 subtable? classdefs = ruleset.format2ClassDefs() @@ -390,21 +384,19 @@ class ChainContextualBuilder(LookupBuilder): candidates[2] = [ self.buildFormat2Subtable(ruleset, classdefs, chaining) ] - # Check we can build it - try: - self.getCompiledSize_(candidates[2]) - except Exception as e: - log.warning("Contextual format 2 at %s overflowed (%s)" % (str(self.location), e)) - candidates[2] = None if not ruleset.hasAnyGlyphClasses: candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)] - # Check we can build it + + for i in [1, 2, 3]: try: - self.getCompiledSize_(candidates[1]) + self.getCompiledSize_(candidates[i]) except Exception as e: - log.warning("Contextual format 1 at %s overflowed" % str(self.location)) - candidates[1] = None + log.warning( + "Contextual format %i at %s overflowed (%s)" + % (i, str(self.location), e) + ) + candidates[i] = None candidates = [x for x in candidates if x is not None] if not candidates: