Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
392a13bb
Commit
392a13bb
authored
Oct 15, 2019
by
Neil Schemenauer
Committed by
GitHub
Oct 15, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-38006: Add unit test for weakref clear bug (GH-16788)
parent
fab4ef2d
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
121 additions
and
0 deletions
+121
-0
Lib/test/test_gc.py
Lib/test/test_gc.py
+66
-0
Modules/_testcapimodule.c
Modules/_testcapimodule.c
+55
-0
No files found.
Lib/test/test_gc.py
View file @
392a13bb
import
unittest
import
unittest.mock
from
test.support
import
(
verbose
,
refcount_test
,
run_unittest
,
strip_python_stderr
,
cpython_only
,
start_threads
,
temp_dir
,
requires_type_collecting
,
TESTFN
,
unlink
,
...
...
@@ -22,6 +23,11 @@ except ImportError:
raise
TypeError
(
'requires _testcapi.with_tp_del'
)
return
C
try
:
from
_testcapi
import
ContainerNoGC
except
ImportError
:
ContainerNoGC
=
None
### Support code
###############################################################################
...
...
@@ -959,6 +965,66 @@ class GCTests(unittest.TestCase):
gc
.
enable
()
@
unittest
.
skipIf
(
ContainerNoGC
is
None
,
'requires ContainerNoGC extension type'
)
def
test_trash_weakref_clear
(
self
):
# Test that trash weakrefs are properly cleared (bpo-38006).
#
# Structure we are creating:
#
# Z <- Y <- A--+--> WZ -> C
# ^ |
# +--+
# where:
# WZ is a weakref to Z with callback C
# Y doesn't implement tp_traverse
# A contains a reference to itself, Y and WZ
#
# A, Y, Z, WZ are all trash. The GC doesn't know that Z is trash
# because Y does not implement tp_traverse. To show the bug, WZ needs
# to live long enough so that Z is deallocated before it. Then, if
# gcmodule is buggy, when Z is being deallocated, C will run.
#
# To ensure WZ lives long enough, we put it in a second reference
# cycle. That trick only works due to the ordering of the GC prev/next
# linked lists. So, this test is a bit fragile.
#
# The bug reported in bpo-38006 is caused because the GC did not
# clear WZ before starting the process of calling tp_clear on the
# trash. Normally, handle_weakrefs() would find the weakref via Z and
# clear it. However, since the GC cannot find Z, WR is not cleared and
# it can execute during delete_garbage(). That can lead to disaster
# since the callback might tinker with objects that have already had
# tp_clear called on them (leaving them in possibly invalid states).
callback
=
unittest
.
mock
.
Mock
()
class
A
:
__slots__
=
[
'a'
,
'y'
,
'wz'
]
class
Z
:
pass
# setup required object graph, as described above
a
=
A
()
a
.
a
=
a
a
.
y
=
ContainerNoGC
(
Z
())
a
.
wz
=
weakref
.
ref
(
a
.
y
.
value
,
callback
)
# create second cycle to keep WZ alive longer
wr_cycle
=
[
a
.
wz
]
wr_cycle
.
append
(
wr_cycle
)
# ensure trash unrelated to this test is gone
gc
.
collect
()
gc
.
disable
()
# release references and create trash
del
a
,
wr_cycle
gc
.
collect
()
# if called, it means there is a bug in the GC. The weakref should be
# cleared before Z dies.
callback
.
assert_not_called
()
gc
.
enable
()
class
GCCallbackTests
(
unittest
.
TestCase
):
def
setUp
(
self
):
# Save gc state and disable it.
...
...
Modules/_testcapimodule.c
View file @
392a13bb
...
...
@@ -6538,6 +6538,53 @@ static PyTypeObject MethStatic_Type = {
"Class with static methods to test calling conventions"
),
};
/* ContainerNoGC -- a simple container without GC methods */
typedef
struct
{
PyObject_HEAD
PyObject
*
value
;
}
ContainerNoGCobject
;
static
PyObject
*
ContainerNoGC_new
(
PyTypeObject
*
type
,
PyObject
*
args
,
PyObject
*
kwargs
)
{
PyObject
*
value
;
char
*
names
[]
=
{
"value"
,
NULL
};
if
(
!
PyArg_ParseTupleAndKeywords
(
args
,
kwargs
,
"O"
,
names
,
&
value
))
{
return
NULL
;
}
PyObject
*
self
=
type
->
tp_alloc
(
type
,
0
);
if
(
self
==
NULL
)
{
return
NULL
;
}
Py_INCREF
(
value
);
((
ContainerNoGCobject
*
)
self
)
->
value
=
value
;
return
self
;
}
static
void
ContainerNoGC_dealloc
(
ContainerNoGCobject
*
self
)
{
Py_DECREF
(
self
->
value
);
Py_TYPE
(
self
)
->
tp_free
((
PyObject
*
)
self
);
}
static
PyMemberDef
ContainerNoGC_members
[]
=
{
{
"value"
,
T_OBJECT
,
offsetof
(
ContainerNoGCobject
,
value
),
READONLY
,
PyDoc_STR
(
"a container value for test purposes"
)},
{
0
}
};
static
PyTypeObject
ContainerNoGC_type
=
{
PyVarObject_HEAD_INIT
(
NULL
,
0
)
"_testcapi.ContainerNoGC"
,
sizeof
(
ContainerNoGCobject
),
.
tp_dealloc
=
(
destructor
)
ContainerNoGC_dealloc
,
.
tp_flags
=
Py_TPFLAGS_DEFAULT
|
Py_TPFLAGS_BASETYPE
,
.
tp_members
=
ContainerNoGC_members
,
.
tp_new
=
ContainerNoGC_new
,
};
static
struct
PyModuleDef
_testcapimodule
=
{
PyModuleDef_HEAD_INIT
,
...
...
@@ -6735,6 +6782,14 @@ PyInit__testcapi(void)
Py_DECREF
(
subclass_with_finalizer_bases
);
PyModule_AddObject
(
m
,
"HeapCTypeSubclassWithFinalizer"
,
HeapCTypeSubclassWithFinalizer
);
if
(
PyType_Ready
(
&
ContainerNoGC_type
)
<
0
)
{
return
NULL
;
}
Py_INCREF
(
&
ContainerNoGC_type
);
if
(
PyModule_AddObject
(
m
,
"ContainerNoGC"
,
(
PyObject
*
)
&
ContainerNoGC_type
)
<
0
)
return
NULL
;
PyState_AddModule
(
m
,
&
_testcapimodule
);
return
m
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment