Skip to content

bpo-32176: Set CO_NOFREE in the code object constructor #4675

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
Dec 3, 2017

Conversation

ncoghlan
Copy link
Contributor

@ncoghlan ncoghlan commented Dec 2, 2017

Previously, CO_NOFREE was set in the compiler, which meant
it could end up being set incorrectly when code objects
were created directly. Setting it in the constructor based
on freevars and cellvars ensures it is always accurate,
regardless of how the code object is defined.

https://bugs.python.org/issue32176

Previously, CO_NOFREE was set in the compiler, which meant
it could end up being set incorrectly when code objects
were created directly. Setting it in the constructor based
on freevars and cellvars ensures it is always accurate,
regardless of how the code object is defined.

# Ensure the implicit super() call actually works
obj = List([1,2,3])
self.assertEqual(obj[0], 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it print to stdout? stdout shouldn't be polluted by tests in non-verbose mode.

How do you know that the injected method actually works instead of the original list method? I suggest to transform the value in __getitem__. For example by calling str().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - replaced the print() with the use of an f-string as the getitem result.

self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE)

# Ensure the implicit super() call actually works
obj = List([1,2,3])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add spaces after commas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))

class List(list):
def append(self, elem):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these methods related to the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copied from the original reproducer in the bug report - replaced with pass.

def new_code(c_or_f):
'''A new code object with a __class__ cell added to freevars'''
c = c_or_f.__code__ if isinstance(c_or_f, FunctionType) else c_or_f
return CodeType(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems there are no direct tests for CodeType. I think we need to add them (in different issue).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that would probably be a good idea. I did a brief review for other unhandled edge cases in direct calls to the constructor and didn't see any, but I could easily have missed something.


def new_code(c_or_f):
'''A new code object with a __class__ cell added to freevars'''
c = c_or_f.__code__ if isinstance(c_or_f, FunctionType) else c_or_f
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test new_code() is called only with the code object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored both this and add_foreign_method to only handle the cases needed for the test (the original more general versions came from the reported reproducer for the issue)

self.assertIs(class_ref, List)

# Ensure the code correctly indicates it accesses a free variable
self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the second argument hex(function.__code__.co_flags).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

- use a non-dunder name for the injected function
- remove some code branches not actually needed for the test
- transform the value to ensure the injected method is running
- don't print anything as a side effect of running the test
- print the code flags as hex if the test assertion fails
Copy link
Contributor Author

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! All suggestions accepted, and I spotted a few other opportunities for cleanups while I was editing.


# Ensure the implicit super() call actually works
obj = List([1,2,3])
self.assertEqual(obj[0], 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - replaced the print() with the use of an f-string as the getitem result.

self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE)

# Ensure the implicit super() call actually works
obj = List([1,2,3])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

self.assertIs(class_ref, List)

# Ensure the code correctly indicates it accesses a free variable
self.assertFalse(function.__code__.co_flags & inspect.CO_NOFREE)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))

class List(list):
def append(self, elem):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copied from the original reproducer in the bug report - replaced with pass.

def new_code(c_or_f):
'''A new code object with a __class__ cell added to freevars'''
c = c_or_f.__code__ if isinstance(c_or_f, FunctionType) else c_or_f
return CodeType(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that would probably be a good idea. I did a brief review for other unhandled edge cases in direct calls to the constructor and didn't see any, but I could easily have missed something.


def new_code(c_or_f):
'''A new code object with a __class__ cell added to freevars'''
c = c_or_f.__code__ if isinstance(c_or_f, FunctionType) else c_or_f
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored both this and add_foreign_method to only handle the cases needed for the test (the original more general versions came from the reported reproducer for the issue)

@ncoghlan ncoghlan merged commit 078f181 into python:master Dec 3, 2017
@miss-islington
Copy link
Contributor

Thanks @ncoghlan for the PR 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Sorry, @ncoghlan, I could not cleanly backport this to 3.6 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 078f1814f1a4413a2a0fdb8cf4490ee0fc98ef34 3.6

@ncoghlan
Copy link
Contributor Author

ncoghlan commented Dec 3, 2017

@serhiy-storchaka Thanks for the review!

ncoghlan added a commit to ncoghlan/cpython that referenced this pull request Dec 3, 2017
…GH-4675)

Previously, CO_NOFREE was set in the compiler, which meant
it could end up being set incorrectly when code objects
were created directly. Setting it in the constructor based
on freevars and cellvars ensures it is always accurate,
regardless of how the code object is defined..
(cherry picked from commit 078f181)
@bedevere-bot
Copy link

GH-4684 is a backport of this pull request to the 3.6 branch.

@ncoghlan ncoghlan deleted the issue-32176-class-cell-injection branch March 30, 2018 07:52
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.

5 participants