8000 WIP: Fix deepcopy-ing of CFFs by madig · Pull Request #1488 · fonttools/fonttools · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

WIP: Fix deepcopy-ing of CFFs #1488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 6, 2019
Merged

WIP: Fix deepcopy-ing of CFFs #1488

merged 3 commits into from
Feb 6, 2019

Conversation

madig
Copy link
Collaborator
@madig madig commented Feb 5, 2019

Deepcopying a TTFont with a CFF table can result in an infinite recursion on Python 3 under certain circumstances.

@anthrotype found that CFFFontSet keeps a reference to the TTFont object that holds it to get at the glyph order at some point: https://github.com/fonttools/fonttools/blob/master/Lib/fontTools/cffLib/__init__.py#L34. Maybe this messes with deepcopy.

@anthrotype
Copy link
Member

I was checking as well during my flight, but I don’t think it’s that. It has to do with the custom BaseDict.__getattr__ method, it seems to not play nicely with deepcopy. I need to study the code a bit more

@madig
Copy link
Collaborator Author
madig commented Feb 5, 2019

Yeah, implementing

	def __deepcopy__(self, memo):
		from copy import deepcopy

		cls = self.__class__
		obj = cls.__new__(cls)
		for k, v in self.__dict__.items():
			if k == "otFont":
				obj.otFont = None
			else:
				obj.__dict__[k] = deepcopy(v, memo)
		return obj

on CFFFontSet doesn't help.

I suppose that it is still problematic to store a reference to the parent TTFont object we want to deepcopy?

@madig
Copy link
Collaborator Author
madig commented Feb 5, 2019

Stepping through this, it seems that BaseDict.__getattr__ is getting repeatedly called with name="rawDict", which executes self.rawDict.get(name, Non 8000 e) (rawDict being a PrivateDict), which in its __getattr__ calls BaseDict.__getattr__, i.e. loops back.

@madig
Copy link
Collaborator Author
madig commented Feb 5, 2019

Actually, there's infinite recursion even when I remove the __getattr__ on PrivateDict. Stopping on the first line of BaseDict's __getattr__ when name is rawDict, even accessing self.rawDict in the debugger causes infinite recursion.

@madig
Copy link
Collaborator Author
madig commented Feb 5, 2019

Maybe because self.rawDict doesn't exist and as such triggers calling __getattr__ on itself, which we overrode to call ourselves.

@anthrotype
Copy link
Member

I have no internet on my laptop so I just sent you a photo of a git patch via WhatsApp LOL 😂

@madig
Copy link
Collaborator Author
madig commented Feb 5, 2019

😁

    def __getattr__(self, name):
>       rawDict = super(BaseDict, self).__getattr__(self, "rawDict")
E    AttributeError: 'super' object has no attribute '__getattr__'

@anthrotype
Copy link
Member

We need to implement custom __getstate__ and __setstate__ for BaseDict so that both deepcopy and pickle work. I can pick up from here

@anthrotype anthrotype merged commit 8832062 into fonttools:master Feb 6, 2019
anthrotype added a commit that referenced this pull request Feb 6, 2019
We need to raise AttributeError for non-existing dunder methods like
'__deepcopy__' or '__getstate__', because deepcopy() and pickle.load()
test for these on the instance using getattr() and treat the resulting
AttributeError as a signal that the object doesn't implement these custom
hooks. If we don't do that, we enter an infinite recursion as we attempt
to look up the missing dunder methods in the 'rawDict' dictionary,
because 'rawDict' is set inside __init__, but __init__ is not invoked
while unpickling (only __new__ is); thus self.rawDict is also missing
and __getattr__ is invoked with argument 'rawDict' again and again until
it crashes with RecursionError. Phew.

Fixes #1488
@anthrotype
Copy link
Member

fixed on master with 649dc49

@madig madig deleted the fix-cff-deepcopy branch February 6, 2019 10:09
justvanrossum pushed a commit to justvanrossum/fonttools that referenced this pull request Mar 3, 2019
We need to raise AttributeError for non-existing dunder methods like
'__deepcopy__' or '__getstate__', because deepcopy() and pickle.load()
test for these on the instance using getattr() and treat the resulting
AttributeError as a signal that the object doesn't implement these custom
hooks. If we don't do that, we enter an infinite recursion as we attempt
to look up the missing dunder methods in the 'rawDict' dictionary,
because 'rawDict' is set inside __init__, but __init__ is not invoked
while unpickling (only __new__ is); thus self.rawDict is also missing
and __getattr__ is invoked with argument 'rawDict' again and again until
it crashes with RecursionError. Phew.

Fixes fonttools#1488
justvanrossum pushed a commit to justvanrossum/fonttools that referenced this pull request Mar 3, 2019
The dunder method doesn't seem to be doing anything other than providing
an `in_cff2` attribute. Do that with a property instead of bending
__getattr__.

This one confused me when I was working on
fonttools#1488.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0