Skip to content

Prohibit assignment to __class__ (Fixes #7724) #19180

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3078,6 +3078,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
with self.enter_final_context(s.is_final_def):
self.check_assignment(s.lvalues[-1], s.rvalue, s.type is None, s.new_syntax)

for lv in s.lvalues:
if isinstance(lv, MemberExpr):
if lv.name == "__class__":
self.fail("Assignment to '__class__' is unsafe and not allowed", lv)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This wording is too strong - assignments are fine themselves, there's nothing inherently unsafe with doing this, it's not a security vuln. Probably something along the lines of

Suggested change
self.fail("Assignment to '__class__' is unsafe and not allowed", lv)
self.fail("Assignment to '__class__' is not supported", lv)

(probably accompanied by a self.note explaining it further) would be less intrusive.


if s.is_alias_def:
self.check_type_alias_rvalue(s)

Expand Down
22 changes: 22 additions & 0 deletions test-data/unit/check-assign-to-class.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[case test_assign_to_dunder_class]
Copy link
Collaborator

Choose a reason for hiding this comment

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

For some historical reason, all our testcases use camelCase naming scheme. Also I don't think this deserves its own file, probably just appending to check-statements.test would be more reasonable.

class A:
def foo(self):
self.__class__ = B # E: Assignment to '__class__' is unsafe and not allowed
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please also add testcases with non-self assignments and assignments to self.__class__ in __init__ (they're slightly different AFAIC).

I'd also love to see tests where this is assigned to various other destinations:

b = object()
b.__class__ = something

class A:
    __class__ = something

# etc.


class B:
pass

[case test_assign_to_other_dunder_attributes]
class C:
def bar(self):
self.__name__ = "NewName" # OK
self.__doc__ = "Test doc" # OK

class D:
pass

[case test_assign_to_regular_attribute]
Copy link
Collaborator

Choose a reason for hiding this comment

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

We certainly already have tests checking that.

class E:
x = 1
def baz(self):
self.x = 2 # OK