summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpeng weikang <pengwk@pengwk.com>2021-04-14 02:38:17 +0800
committerStefan Behnel <stefan_ml@behnel.de>2021-04-14 13:37:06 +0200
commiteb6d31a5e3046d7d7d324a5861496609135cc2c1 (patch)
treeaa8eccd727ac3af9eb4f157d3417d08dbc30ea3b
parent3ae58fea907a5abbc34f1792242a884c194ada5e (diff)
downloadcython-eb6d31a5e3046d7d7d324a5861496609135cc2c1.tar.gz
Remove incorrect dict unpacking optimisation that leaked external dict changes into the result (GH-4091)
Closes https://github.com/cython/cython/issues/3227
-rw-r--r--Cython/Compiler/ExprNodes.py11
-rw-r--r--tests/run/dict.pyx19
-rw-r--r--tests/run/kwargs_passthrough.pyx32
3 files changed, 42 insertions, 20 deletions
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 86bfa21c7..4b424d50e 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -6669,22 +6669,13 @@ class MergedDictNode(ExprNode):
return dict_type
def analyse_types(self, env):
- args = [
+ self.keyword_args = [
arg.analyse_types(env).coerce_to_pyobject(env).as_none_safe_node(
# FIXME: CPython's error message starts with the runtime function name
'argument after ** must be a mapping, not NoneType')
for arg in self.keyword_args
]
- if len(args) == 1 and args[0].type is dict_type:
- # strip this intermediate node and use the bare dict
- arg = args[0]
- if arg.is_name and arg.entry.is_arg and len(arg.entry.cf_assignments) == 1:
- # passing **kwargs through to function call => allow NULL
- arg.allow_null = True
- return arg
-
- self.keyword_args = args
return self
def may_be_none(self):
diff --git a/tests/run/dict.pyx b/tests/run/dict.pyx
index d51cef7da..691b78635 100644
--- a/tests/run/dict.pyx
+++ b/tests/run/dict.pyx
@@ -117,3 +117,22 @@ def item_creation_sideeffect(L, sideeffect, unhashable):
[2, 4, 5]
"""
return {1:2, sideeffect(2): 3, 3: 4, unhashable(4): 5, sideeffect(5): 6}
+
+
+def dict_unpacking_not_for_arg_create_a_copy():
+ """
+ >>> dict_unpacking_not_for_arg_create_a_copy()
+ [('a', 'modified'), ('b', 'original')]
+ [('a', 'original'), ('b', 'original')]
+ """
+ data = {'a': 'original', 'b': 'original'}
+
+ func = lambda: {**data}
+
+ call_once = func()
+ call_once['a'] = 'modified'
+
+ call_twice = func()
+
+ print(sorted(call_once.items()))
+ print(sorted(call_twice.items()))
diff --git a/tests/run/kwargs_passthrough.pyx b/tests/run/kwargs_passthrough.pyx
index 704638a55..576306efd 100644
--- a/tests/run/kwargs_passthrough.pyx
+++ b/tests/run/kwargs_passthrough.pyx
@@ -1,7 +1,6 @@
-cimport cython
+import cython
-
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def wrap_passthrough(f):
"""
>>> def f(a=1): return a
@@ -80,7 +79,7 @@ def wrap_passthrough_more(f):
return wrapper
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def wrap_passthrough2(f):
"""
>>> def f(a=1): return a
@@ -99,7 +98,7 @@ def wrap_passthrough2(f):
return wrapper
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def wrap_modify(f):
"""
>>> def f(a=1, test=2):
@@ -123,7 +122,7 @@ def wrap_modify(f):
return wrapper
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def wrap_modify_mix(f):
"""
>>> def f(a=1, test=2):
@@ -175,7 +174,21 @@ def wrap_modify_func(f):
return wrapper
-@cython.test_assert_path_exists('//MergedDictNode')
+def modify_in_function():
+ """
+ >>> modify_in_function()
+ {'foo': 'bar'}
+ {'foo': 'bar'}
+ """
+ def inner(**kwds):
+ kwds['foo'] = 'modified'
+ d = {'foo': 'bar'}
+ print(d)
+ inner(**d)
+ print(d)
+
+
+#@cython.test_assert_path_exists('//MergedDictNode')
def wrap_modify_func_mix(f):
"""
>>> def f(a=1, test=2):
@@ -203,12 +216,11 @@ def wrap_modify_func_mix(f):
return wrapper
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def wrap_reassign(f):
"""
>>> def f(a=1, test=2):
... return a, test
-
>>> wrapped = wrap_reassign(f)
>>> wrapped(1)
CALLED
@@ -227,7 +239,7 @@ def wrap_reassign(f):
return wrapper
-@cython.test_fail_if_path_exists('//MergedDictNode')
+#@cython.test_fail_if_path_exists('//MergedDictNode')
def kwargs_metaclass(**kwargs):
"""
>>> K = kwargs_metaclass()