@@ -305,6 +305,174 @@ def _index_fields(ary, fields):
305305 copy_dtype = {'names' :view_dtype ['names' ], 'formats' :view_dtype ['formats' ]}
306306 return array (view , dtype = copy_dtype , copy = True )
307307
308+ def _get_all_field_offsets (dtype , base_offset = 0 ):
309+ """ Returns the types and offsets of all fields in a (possibly structured)
310+ data type, including nested fields and subarrays.
311+
312+ Parameters
313+ ----------
314+ dtype : data-type
315+ Data type to extract fields from.
316+ base_offset : int, optional
317+ Additional offset to add to all field offsets.
318+
319+ Returns
320+ -------
321+ fields : list of (data-type, int) pairs
322+ A flat list of (dtype, byte offset) pairs.
323+
324+ """
325+ fields = []
326+ if dtype .fields is not None :
327+ for name in dtype .names :
328+ sub_dtype = dtype .fields [name ][0 ]
329+ sub_offset = dtype .fields [name ][1 ] + base_offset
330+ fields .extend (_get_all_field_offsets (sub_dtype , sub_offset ))
331+ else :
332+ if dtype .shape :
333+ sub_offsets = _get_all_field_offsets (dtype .base , base_offset )
334+ count = 1
335+ for dim in dtype .shape :
336+ count *= dim
337+ fields .extend ((typ , off + dtype .base .itemsize * j )
338+ for j in range (count ) for (typ , off ) in sub_offsets )
339+ else :
340+ fields .append ((dtype , base_offset ))
341+ return fields
342+
343+ def _check_field_overlap (new_fields , old_fields ):
344+ """ Perform object memory overlap tests for two data-types (see
345+ _view_is_safe).
346+
347+ This function checks that new fields only access memory contained in old
348+ fields, and that non-object fields are not interpreted as objects and vice
349+ versa.
350+
351+ Parameters
352+ ----------
353+ new_fields : list of (data-type, int) pairs
354+ Flat list of (dtype, byte offset) pairs for the new data type, as
355+ returned by _get_all_field_offsets.
356+ old_fields: list of (data-type, int) pairs
357+ Flat list of (dtype, byte offset) pairs for the old data type, as
358+ returned by _get_all_field_offsets.
359+
360+ Raises
361+ ------
362+ TypeError
363+ If the new fields are incompatible with the old fields
364+
365+ """
366+ from .numerictypes import object_
367+ from .multiarray import dtype
368+
369+ #first go byte by byte and check we do not access bytes not in old_fields
370+ new_bytes = set ()
371+ for tp , off in new_fields :
372+ new_bytes .update (set (range (off , off + tp .itemsize )))
373+ old_bytes = set ()
374+ for tp , off in old_fields :
375+ old_bytes .update (set (range (off , off + tp .itemsize )))
376+ if new_bytes .difference (old_bytes ):
377+ raise TypeError ("view would access data parent array doesn't own" )
378+
379+ #next check that we do not interpret non-Objects as Objects, and vv
380+ obj_offsets = [off for (tp , off ) in old_fields if tp .type is object_ ]
381+ obj_size = dtype (object_ ).itemsize
382+
383+ for fld_dtype , fld_offset in new_fields :
384+ if fld_dtype .type is object_ :
385+ # check we do not create object views where
386+ # there are no objects.
387+ if fld_offset not in obj_offsets :
388+ raise TypeError ("cannot view non-Object data as Object type" )
389+ else :
390+ # next check we do not create non-object views
391+ # where there are already objects.
392+ # see validate_object_field_overlap for a similar computation.
393+ for obj_offset in obj_offsets :
394+ if (fld_offset < obj_offset + obj_size and
395+ obj_offset < fld_offset + fld_dtype .itemsize ):
396+ raise TypeError ("cannot view Object as non-Object type" )
397+
398+ def _getfield_is_safe (oldtype , newtype , offset ):
399+ """ Checks safety of getfield for object arrays.
400+
401+ As in _view_is_safe, we need to check that memory containing objects is not
402+ reinterpreted as a non-object datatype and vice versa.
403+
404+ Parameters
405+ ----------
406+ oldtype : data-type
407+ Data type of the original ndarray.
408+ newtype : data-type
409+ Data type of the field being accessed by ndarray.getfield
410+ offset : int
411+ Offset of the field being accessed by ndarray.getfield
412+
413+ Raises
414+ ------
415+ TypeError
416+ If the field access is invalid
417+
418+ """
419+ new_fields = _get_all_field_offsets (newtype , offset )
420+ old_fields = _get_all_field_offsets (oldtype )
421+ # raises if there is a problem
422+ _check_field_overlap (new_fields , old_fields )
423+
424+ def _view_is_safe (oldtype , newtype ):
425+ """ Checks safety of a view involving object arrays, for example when
426+ doing::
427+
428+ np.zeros(10, dtype=oldtype).view(newtype)
429+
430+ We need to check that
431+ 1) No memory that is not an object will be interpreted as a object,
432+ 2) No memory containing an object will be interpreted as an arbitrary type.
433+ Both cases can cause segfaults, eg in the case the view is written to.
434+ Strategy here is to also disallow views where newtype has any field in a
435+ place oldtype doesn't.
436+
437+ Parameters
438+ ----------
439+ oldtype : data-type
440+ Data type of original ndarray
441+ newtype : data-type
442+ Data type of the view
443+
444+ Raises
445+ ------
446+ TypeError
447+ If the new type is incompatible with the old type.
448+
449+ """
450+ new_fields = _get_all_field_offsets (newtype )
451+ new_size = newtype .itemsize
452+
453+ old_fields = _get_all_field_offsets (oldtype )
454+ old_size = oldtype .itemsize
455+
456+ # if the itemsizes are not equal, we need to check that all the
457+ # 'tiled positions' of the object match up. Here, we allow
458+ # for arbirary itemsizes (even those possibly disallowed
459+ # due to stride/data length issues).
460+ if old_size == new_size :
461+ new_num = old_num = 1
462+ else :
463+ gcd_new_old = _gcd (new_size , old_size )
464+ new_num = old_size // gcd_new_old
465+ old_num = new_size // gcd_new_old
466+
467+ # get position of fields within the tiling
468+ new_fieldtile = [(tp , off + new_size * j )
469+ for j in range (new_num ) for (tp , off ) in new_fields ]
470+ old_fieldtile = [(tp , off + old_size * j )
471+ for j in range (old_num ) for (tp , off ) in old_fields ]
472+
473+ # raises if there is a problem
474+ _check_field_overlap (new_fieldtile , old_fieldtile )
475+
308476# Given a string containing a PEP 3118 format specifier,
309477# construct a Numpy dtype
310478
0 commit comments