8000 [3.8] bpo-16575: Add checks for unions passed by value to functions. … · python/cpython@9528997 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9528997

Browse files
authored
[3.8] bpo-16575: Add checks for unions passed by value to functions. (GH-16799) (GH-17016)
(cherry picked from commit 79d4ed1)
1 parent c1ebe6a commit 9528997

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

Lib/ctypes/test/test_structures.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,86 @@ class U(Union):
571571
self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab,
572572
0x3210, 0x7654, 0xba98, 0xfedc])
573573

574+
def test_union_by_value(self):
575+
# See bpo-16575
576+
577+
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
578+
579+
class Nested1(Structure):
580+
_fields_ = [
581+
('an_int', c_int),
582+
('another_int', c_int),
583+
]
584+
585+
class Test4(Union):
586+
_fields_ = [
587+
('a_long', c_long),
588+
('a_struct', Nested1),
589+
]
590+
591+
class Nested2(Structure):
592+
_fields_ = [
593+
('an_int', c_int),
594+
('a_union', Test4),
595+
]
596+
597+
class Test5(Structure):
598+
_fields_ = [
599+
('an_int', c_int),
600+
('nested', Nested2),
601+
('another_int', c_int),
602+
]
603+
604+
test4 = Test4()
605+
dll = CDLL(_ctypes_test.__file__)
606+
with self.assertRaises(TypeError) as ctx:
607+
func = dll._testfunc_union_by_value1
608+
func.restype = c_long
609+
func.argtypes = (Test4,)
610+
result = func(test4)
611+
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
612+
'a union by value, which is unsupported.')
613+
test5 = Test5()
614+
with self.assertRaises(TypeError) as ctx:
615+
func = dll._testfunc_union_by_value2
616+
func.restype = c_long
617+
func.argtypes = (Test5,)
618+
result = func(test5)
619+
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
620+
'a union by value, which is unsupported.')
621+
622+
# passing by reference should be OK
623+
test4.a_long = 12345;
624+
func = dll._testfunc_union_by_reference1
625+
func.restype = c_long
626+
func.argtypes = (POINTER(Test4),)
627+
result = func(byref(test4))
628+
self.assertEqual(result, 12345)
629+
self.assertEqual(test4.a_long, 0)
630+
self.assertEqual(test4.a_struct.an_int, 0)
631+
self.assertEqual(test4.a_struct.another_int, 0)
632+
test4.a_struct.an_int = 0x12340000
633+
test4.a_struct.another_int = 0x5678
634+
func = dll._testfunc_union_by_reference2
635+
func.restype = c_long
636+
func.argtypes = (POINTER(Test4),)
637+
result = func(byref(test4))
638+
self.assertEqual(result, 0x12345678)
639+
self.assertEqual(test4.a_long, 0)
640+
self.assertEqual(test4.a_struct.an_int, 0)
641+
self.assertEqual(test4.a_struct.another_int, 0)
642+
test5.an_int = 0x12000000
643+
test5.nested.an_int = 0x345600
644+
test5.another_int = 0x78
645+
func = dll._testfunc_union_by_reference3
646+
func.restype = c_long
647+
func.argtypes = (POINTER(Test5),)
648+
result = func(byref(test5))
649+
self.assertEqual(result, 0x12345678)
650+
self.assertEqual(test5.an_int, 0)
651+
self.assertEqual(test5.nested.an_int, 0)
652+
self.assertEqual(test5.another_int, 0)
653+
574654
class PointerMemberTestCase(unittest.TestCase):
575655

576656
def test(self):

Modules/_ctypes/_ctypes.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ StructUnionType_new(PyTypeObject *type, PyObject *args, PyObject *kwds, int isSt
504504
Py_DECREF(result);
505505
return NULL;
506506
}
507+
if (!isStruct) {
508+
dict->flags |= TYPEFLAG_HASUNION;
509+
}
507510
/* replace the class dict by our updated stgdict, which holds info
508511
about storage requirements of the instances */
509512
if (-1 == PyDict_Update((PyObject *)dict, result->tp_dict)) {
@@ -2383,6 +2386,27 @@ converters_from_argtypes(PyObject *ob)
23832386
for (i = 0; i < nArgs; ++i) {
23842387
PyObject *cnv;
23852388
PyObject *tp = PyTuple_GET_ITEM(ob, i);
2389+
StgDictObject *stgdict = PyType_stgdict(tp);
2390+
2391+
if (stgdict != NULL) {
2392+
if (stgdict->flags & TYPEFLAG_HASUNION) {
2393+
Py_DECREF(converters);
2394+
Py_DECREF(ob);
2395+
if (!PyErr_Occurred()) {
2396+
PyErr_Format(PyExc_TypeError,
2397+
"item %zd in _argtypes_ passes a union by "
2398+
"value, which is unsupported.",
2399+
i + 1);
2400+
}
2401+
return NULL;
2402+
}
2403+
/*
2404+
if (stgdict->flags & TYPEFLAG_HASBITFIELD) {
2405+
printf("found stgdict with bitfield\n");
2406+
}
2407+
*/
2408+
}
2409+
23862410
if (_PyObject_LookupAttrId(tp, &PyId_from_param, &cnv) <= 0) {
23872411
Py_DECREF(converters);
23882412
Py_DECREF(ob);

Modules/_ctypes/_ctypes_test.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,69 @@ _testfunc_array_in_struct2a(Test3B in)
135135
return result;
136136
}
137137

138+
typedef union {
139+
long a_long;
140+
struct {
141+
int an_int;
142+
int another_int;
143+
} a_struct;
144+
} Test4;
145+
146+
typedef struct {
147+
int an_int;
148+
struct {
149+
int an_int;
150+
Test4 a_union;
151+
} nested;
152+
int another_int;
153+
} Test5;
154+
155+
EXPORT(long)
156+
_testfunc_union_by_value1(Test4 in) {
157+
long result = in.a_long + in.a_struct.an_int + in.a_struct.another_int;
158+
159+
/* As the union/struct are passed by value, changes to them shouldn't be
160+
* reflected in the caller.
161+
*/
162+
memset(&in, 0, sizeof(in));
163+
return result;
164+
}
165+
166+
EXPORT(long)
167+
_testfunc_union_by_value2(Test5 in) {
168+
long result = in.an_int + in.nested.an_int;
169+
170+
/* As the union/struct are passed by value, changes to them shouldn't be
171+
* reflected in the caller.
172+
*/
173+
memset(&in, 0, sizeof(in));
174+
return result;
175+
}
176+
177+
EXPORT(long)
178+
_testfunc_union_by_reference1(Test4 *in) {
179+
long result = in->a_long;
180+
181+
memset(in, 0, sizeof(Test4));
182+
return result;
183+
}
184+
185+
EXPORT(long)
186+
_testfunc_union_by_reference2(Test4 *in) {
187+
long result = in->a_struct.an_int + in->a_struct.another_int;
188+
189+
memset(in, 0, sizeof(Test4));
190+
return result;
191+
}
192+
193+
EXPORT(long)
194+
_testfunc_union_by_reference3(Test5 *in) {
195+
long result = in->an_int + in->nested.an_int + in->another_int;
196+
197+
memset(in, 0, sizeof(Test5));
198+
return result;
199+
}
200+
138201
EXPORT(void)testfunc_array(int values[4])
139202
{
140203
printf("testfunc_array %d %d %d %d\n",

Modules/_ctypes/ctypes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ PyObject *_ctypes_callproc(PPROC pProc,
288288

289289
#define TYPEFLAG_ISPOINTER 0x100
290290
#define TYPEFLAG_HASPOINTER 0x200
291+
#define TYPEFLAG_HASUNION 0x400
292+
#define TYPEFLAG_HASBITFIELD 0x800
291293

292294
#define DICTFLAG_FINAL 0x1000
293295

Modules/_ctypes/stgdict.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,13 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
440440
PyMem_Free(stgdict->ffi_type_pointer.elements);
441441

442442
basedict = PyType_stgdict((PyObject *)((PyTypeObject *)type)->tp_base);
443+
if (basedict) {
444+
stgdict->flags |= (basedict->flags &
445+
(TYPEFLAG_HASUNION | TYPEFLAG_HASBITFIELD));
446+
}
447+
if (!isStruct) {
448+
stgdict->flags |= TYPEFLAG_HASUNION;
449+
}
443450
if (basedict && !use_broken_old_ctypes_semantics) {
444451
size = offset = basedict->size;
445452
align = basedict->align;
@@ -515,8 +522,10 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
515522
stgdict->ffi_type_pointer.elements[ffi_ofs + i] = &dict->ffi_type_pointer;
516523
if (dict->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER))
517524
stgdict->flags |= TYPEFLAG_HASPOINTER;
525+
stgdict->flags |= dict->flags & (TYPEFLAG_HASUNION | TYPEFLAG_HASBITFIELD);
518526
dict->flags |= DICTFLAG_FINAL; /* mark field type final */
519527
if (PyTuple_Size(pair) == 3) { /* bits specified */
528+
stgdict->flags |= TYPEFLAG_HASBITFIELD;
520529
switch(dict->ffi_type_pointer.type) {
521530
case FFI_TYPE_UINT8:
522531
case FFI_TYPE_UINT16:

0 commit comments

Comments
 (0)
0