Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ def visit_set_attr(self, op: SetAttr) -> None:
op.attr,
)
)
# The property setter method increfs the passed value (src) so we need to decref it here
# to avoid leaking.
src_rtype = op.src.type
if src_rtype.is_refcounted:
self.emitter.emit_dec_ref(src, src_rtype)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A potentially cleaner approach would be to adjust stolen of SetAttr for properties -- i.e. don't steal the value if it's a property. Thoughts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ah good call, this way the decref is visible in the IR which makes for better tests.

i've removed the leak test from the run test as it seemed a little flaky when running it multiple times in a row.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

looks like the failures were actually an implementation mistake - using ClassIR.methods during IR generation may not work in multimodule compilation because the SetAttr constructor might run before the property setter method is added in the other module.

i've changed it to use ClassIR.method_decls instead which is populated in prepare.py so it's safe to use.

else:
# ...and struct access for normal attributes.
attr_expr = self.get_attr_expr(obj, op, decl_cl)
Expand Down
37 changes: 36 additions & 1 deletion mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2160,8 +2160,40 @@ class H:

[file other.py]
# Run in both interpreted and compiled mode
import gc

from native import A, B, C, D, E, F, Foo, G

def test_leaks() -> None:
a = A()

gc.collect()
before = gc.get_objects()
for _ in range(100):
a.foo = Foo()
gc.collect()
after = gc.get_objects()

diff = len(after) - len(before)
# Small difference is expected, if the property setter call was leaking objects
# we would get a difference similar to the number of iterations above.
assert diff <= 2, diff

from native import A, B, C, D, E, F, G
def set_foo(foo: Foo) -> None:
a = A()
a.foo = foo

def test_use_after_passing_to_prop_setter() -> None:
foo = Foo()

foo.attr = "before"
assert foo.attr == "before", foo.attr

set_foo(foo)
assert foo.attr == "before", foo.attr

foo.attr = "after"
assert foo.attr == "after", foo.attr

a = A()
assert a.x == 0
Expand Down Expand Up @@ -2195,6 +2227,9 @@ g = G(4)
g.x = 20
assert g.x == 20

test_leaks()
test_use_after_passing_to_prop_setter()

[file driver.py]
# Run the tests in both interpreted and compiled mode
import other
Expand Down
Loading