Commit c9eda12b authored by Stefan Behnel's avatar Stefan Behnel

Rename the options of the "check_size" feature to make them more obvious:...

Rename the options of the "check_size" feature to make them more obvious: "warn" warns, "error" fails, and "extend" silently allows extending.
Closes #2627.
parent f75b8e8f
...@@ -3062,11 +3062,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -3062,11 +3062,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# check_size # check_size
if not type.is_external or type.is_subclassed: if not type.is_external or type.is_subclassed:
cs = 0 cs = 0
elif type.check_size == 'min': elif type.check_size == 'error':
cs = 1
elif type.check_size == True:
cs = 0 cs = 0
elif type.check_size == False: elif type.check_size == 'warn':
cs = 1
elif type.check_size == 'extend':
cs = 2 cs = 2
else: else:
raise RuntimeError("invalid value for check_size '%s' when compiling %s.%s" % ( raise RuntimeError("invalid value for check_size '%s' when compiling %s.%s" % (
......
...@@ -4630,7 +4630,7 @@ class CClassDefNode(ClassDefNode): ...@@ -4630,7 +4630,7 @@ class CClassDefNode(ClassDefNode):
# bases TupleNode Base class(es) # bases TupleNode Base class(es)
# objstruct_name string or None Specified C name of object struct # objstruct_name string or None Specified C name of object struct
# typeobj_name string or None Specified C name of type object # typeobj_name string or None Specified C name of type object
# check_size 'min' or boolean What to do if tp_basicsize does not match # check_size 'warn', 'error', 'extend' What to do if tp_basicsize does not match
# in_pxd boolean Is in a .pxd file # in_pxd boolean Is in a .pxd file
# decorators [DecoratorNode] list of decorators or None # decorators [DecoratorNode] list of decorators or None
# doc string or None # doc string or None
...@@ -4647,7 +4647,7 @@ class CClassDefNode(ClassDefNode): ...@@ -4647,7 +4647,7 @@ class CClassDefNode(ClassDefNode):
api = False api = False
objstruct_name = None objstruct_name = None
typeobj_name = None typeobj_name = None
check_size = 'min' check_size = None
decorators = None decorators = None
shadow = False shadow = False
......
...@@ -3512,8 +3512,6 @@ def p_c_class_definition(s, pos, ctx): ...@@ -3512,8 +3512,6 @@ def p_c_class_definition(s, pos, ctx):
error(pos, "Type object name specification required for 'api' C class") error(pos, "Type object name specification required for 'api' C class")
else: else:
error(pos, "Invalid class visibility '%s'" % ctx.visibility) error(pos, "Invalid class visibility '%s'" % ctx.visibility)
if check_size is None:
check_size = 'min' # TODO: move into 'CClassDefNode'
return Nodes.CClassDefNode(pos, return Nodes.CClassDefNode(pos,
visibility = ctx.visibility, visibility = ctx.visibility,
typedef_flag = ctx.typedef_flag, typedef_flag = ctx.typedef_flag,
...@@ -3547,18 +3545,12 @@ def p_c_class_options(s): ...@@ -3547,18 +3545,12 @@ def p_c_class_options(s):
elif s.systring == 'check_size': elif s.systring == 'check_size':
s.next() s.next()
check_size = p_ident(s) check_size = p_ident(s)
if check_size == 'False': if check_size not in ('extend', 'warn', 'error'):
check_size = False s.error("Expected one of extend, warn or error, found %r" % check_size)
elif check_size == 'True':
check_size = True
elif check_size == 'min':
pass
else:
s.error('Expected False, True, or min, not %r' % check_size)
if s.sy != ',': if s.sy != ',':
break break
s.next() s.next()
s.expect(']', "Expected 'object' or 'type'") s.expect(']', "Expected 'object', 'type' or 'check_size'")
return objstruct_name, typeobj_name, check_size return objstruct_name, typeobj_name, check_size
......
...@@ -1345,7 +1345,7 @@ class PyExtensionType(PyObjectType): ...@@ -1345,7 +1345,7 @@ class PyExtensionType(PyObjectType):
# vtable_cname string Name of C method table definition # vtable_cname string Name of C method table definition
# early_init boolean Whether to initialize early (as opposed to during module execution). # early_init boolean Whether to initialize early (as opposed to during module execution).
# defered_declarations [thunk] Used to declare class hierarchies in order # defered_declarations [thunk] Used to declare class hierarchies in order
# check_size 'min' or boolean What to do if tp_basicsize does not match # check_size 'warn', 'error', 'extend' What to do if tp_basicsize does not match
is_extension_type = 1 is_extension_type = 1
has_attributes = 1 has_attributes = 1
...@@ -1353,7 +1353,7 @@ class PyExtensionType(PyObjectType): ...@@ -1353,7 +1353,7 @@ class PyExtensionType(PyObjectType):
objtypedef_cname = None objtypedef_cname = None
def __init__(self, name, typedef_flag, base_type, is_external=0, check_size='min'): def __init__(self, name, typedef_flag, base_type, is_external=0, check_size=None):
self.name = name self.name = name
self.scope = None self.scope = None
self.typedef_flag = typedef_flag self.typedef_flag = typedef_flag
...@@ -1369,7 +1369,7 @@ class PyExtensionType(PyObjectType): ...@@ -1369,7 +1369,7 @@ class PyExtensionType(PyObjectType):
self.vtabptr_cname = None self.vtabptr_cname = None
self.vtable_cname = None self.vtable_cname = None
self.is_external = is_external self.is_external = is_external
self.check_size = check_size self.check_size = check_size or 'warn'
self.defered_declarations = [] self.defered_declarations = []
def set_scope(self, scope): def set_scope(self, scope):
......
...@@ -1482,7 +1482,7 @@ class ModuleScope(Scope): ...@@ -1482,7 +1482,7 @@ class ModuleScope(Scope):
def declare_c_class(self, name, pos, defining=0, implementing=0, def declare_c_class(self, name, pos, defining=0, implementing=0,
module_name=None, base_type=None, objstruct_cname=None, module_name=None, base_type=None, objstruct_cname=None,
typeobj_cname=None, typeptr_cname=None, visibility='private', typeobj_cname=None, typeptr_cname=None, visibility='private',
typedef_flag=0, api=0, check_size='min', typedef_flag=0, api=0, check_size=None,
buffer_defaults=None, shadow=0): buffer_defaults=None, shadow=0):
# If this is a non-extern typedef class, expose the typedef, but use # If this is a non-extern typedef class, expose the typedef, but use
# the non-typedef struct internally to avoid needing forward # the non-typedef struct internally to avoid needing forward
......
...@@ -203,7 +203,7 @@ cdef extern from "numpy/arrayobject.h": ...@@ -203,7 +203,7 @@ cdef extern from "numpy/arrayobject.h":
ctypedef struct PyArray_Descr: ctypedef struct PyArray_Descr:
pass pass
ctypedef class numpy.dtype [object PyArray_Descr, check_size False]: ctypedef class numpy.dtype [object PyArray_Descr, check_size extend]:
# Use PyDataType_* macros when possible, however there are no macros # Use PyDataType_* macros when possible, however there are no macros
# for accessing some of the fields, so some are defined. # for accessing some of the fields, so some are defined.
cdef PyTypeObject* typeobj cdef PyTypeObject* typeobj
...@@ -239,7 +239,7 @@ cdef extern from "numpy/arrayobject.h": ...@@ -239,7 +239,7 @@ cdef extern from "numpy/arrayobject.h":
# like PyArrayObject**. # like PyArrayObject**.
pass pass
ctypedef class numpy.ndarray [object PyArrayObject, check_size False]: ctypedef class numpy.ndarray [object PyArrayObject, check_size extend]:
cdef __cythonbufferdefaults__ = {"mode": "strided"} cdef __cythonbufferdefaults__ = {"mode": "strided"}
cdef: cdef:
......
...@@ -319,11 +319,11 @@ static PyTypeObject *__Pyx_ImportType(PyObject *module, const char *module_name, ...@@ -319,11 +319,11 @@ static PyTypeObject *__Pyx_ImportType(PyObject *module, const char *module_name,
size_t size, int check_size) size_t size, int check_size)
{ {
/* /*
* check_size tells what to do if tp_basicsize is different from size: * 'check_size' tells what to do if tp_basicsize is different from size:
* 0 - Error (originates in check_size=True) * 0 - Error (originates in check_size=error)
* 1 - Error if tp_basicsize is smaller, warn if larger (originates in check_size='min') * 1 - Error if tp_basicsize is smaller, warn if larger (originates in check_size=warn)
* 2 - Error if tp_basicsize is smaller (originates in check_size=False) * 2 - Error if tp_basicsize is smaller, but allow compatible extensions (originates in check_size=extend)
*/ */
PyObject *result = 0; PyObject *result = 0;
char warning[200]; char warning[200];
Py_ssize_t basicsize; Py_ssize_t basicsize;
......
...@@ -771,14 +771,15 @@ Where: ...@@ -771,14 +771,15 @@ Where:
- ``object_struct_name`` is the name to assume for the type's C struct. - ``object_struct_name`` is the name to assume for the type's C struct.
- ``type_object_name`` is the name to assume for the type's statically - ``type_object_name`` is the name to assume for the type's statically
declared type object. declared type object.
- ``cs_option`` is ``min`` (the default), ``True``, or ``False`` and is only - ``cs_option`` is ``warn`` (the default), ``error``, or ``extend`` and is only
used for external extension types. If ``True``, ``sizeof(object_struct)`` must used for external extension types. If ``error``, the ``sizeof(object_struct)``
match the type's ``tp_basicsize``. If ``False``, or ``min``, the that was found at compile time must match the type's runtime ``tp_basicsize``
``object_struct`` may be smaller than the type's ``tp_basicsize``, which exactly, otherwise the module import will fail with an error. If ``warn``
indicates the type allocated at runtime may be part of an updated module, and or ``extend``, the ``object_struct`` is allowed to be smaller than the type's
that the external module's developers extended the object in a ``tp_basicsize``, which indicates the runtime type may be part of an updated
backward-compatible fashion (only adding new fields to the end of the module, and that the external module's developers extended the object in a
object). If ``min``, a warning will be emitted. backward-compatible fashion (only adding new fields to the end of the object).
If ``warn``, a warning will be emitted in this case.
The clauses can be written in any order. The clauses can be written in any order.
......
...@@ -13,7 +13,7 @@ setup(ext_modules= cythonize("check_size.pyx")) ...@@ -13,7 +13,7 @@ setup(ext_modules= cythonize("check_size.pyx"))
setup(ext_modules = cythonize("_check_size*.pyx")) setup(ext_modules = cythonize("_check_size*.pyx"))
try: try:
setup(ext_modules= cythonize("check_size6.pyx")) setup(ext_modules= cythonize("check_size_invalid.pyx"))
assert False assert False
except CompileError as e: except CompileError as e:
pass pass
...@@ -86,7 +86,7 @@ cdef class Foo: ...@@ -86,7 +86,7 @@ cdef class Foo:
self.field1 = f1 self.field1 = f1
self.field2 = f2 self.field2 = f2
######## _check_size0.pyx ######## ######## _check_size_exact.pyx ########
cdef extern from "check_size_nominal.h": cdef extern from "check_size_nominal.h":
...@@ -99,7 +99,7 @@ cdef extern from "check_size_nominal.h": ...@@ -99,7 +99,7 @@ cdef extern from "check_size_nominal.h":
cpdef public int testme(Foo f) except -1: cpdef public int testme(Foo f) except -1:
return f.f0 + f.f1 return f.f0 + f.f1
######## _check_size1.pyx ######## ######## _check_size_too_small.pyx ########
cdef extern from "check_size_bigger.h": cdef extern from "check_size_bigger.h":
...@@ -114,7 +114,7 @@ cpdef public int testme(Foo f, int f2) except -1: ...@@ -114,7 +114,7 @@ cpdef public int testme(Foo f, int f2) except -1:
f.f2 = f2 f.f2 = f2
return f.f0 + f.f1 + f.f2 return f.f0 + f.f1 + f.f2
######## _check_size2.pyx ######## ######## _check_size_default.pyx ########
cdef extern from "check_size_smaller.h": cdef extern from "check_size_smaller.h":
...@@ -126,12 +126,12 @@ cdef extern from "check_size_smaller.h": ...@@ -126,12 +126,12 @@ cdef extern from "check_size_smaller.h":
cpdef public int testme(Foo f) except -1: cpdef public int testme(Foo f) except -1:
return f.f9 return f.f9
######## _check_size3.pyx ######## ######## _check_size_warn.pyx ########
cdef extern from "check_size_smaller.h": cdef extern from "check_size_smaller.h":
# make sure missing check_size is equivalent to min # make sure missing check_size is equivalent to warn
ctypedef class check_size.Foo [object FooStructSmall, check_size min]: ctypedef class check_size.Foo [object FooStructSmall, check_size warn]:
cdef: cdef:
int f9 int f9
...@@ -139,12 +139,12 @@ cdef extern from "check_size_smaller.h": ...@@ -139,12 +139,12 @@ cdef extern from "check_size_smaller.h":
cpdef public int testme(Foo f) except -1: cpdef public int testme(Foo f) except -1:
return f.f9 return f.f9
######## _check_size4.pyx ######## ######## _check_size_extend.pyx ########
cdef extern from "check_size_smaller.h": cdef extern from "check_size_smaller.h":
# Disable size check # Allow size to be larger
ctypedef class check_size.Foo [object FooStructSmall, check_size False]: ctypedef class check_size.Foo [object FooStructSmall, check_size extend]:
cdef: cdef:
int f9 int f9
...@@ -152,12 +152,12 @@ cdef extern from "check_size_smaller.h": ...@@ -152,12 +152,12 @@ cdef extern from "check_size_smaller.h":
cpdef public int testme(Foo f) except -1: cpdef public int testme(Foo f) except -1:
return f.f9 return f.f9
######## _check_size5.pyx ######## ######## _check_size_error.pyx ########
cdef extern from "check_size_smaller.h": cdef extern from "check_size_smaller.h":
# Strict checking, will raise an error # Strict checking, will raise an error
ctypedef class check_size.Foo [object FooStructSmall, check_size True]: ctypedef class check_size.Foo [object FooStructSmall, check_size error]:
cdef: cdef:
int f9 int f9
...@@ -165,12 +165,12 @@ cdef extern from "check_size_smaller.h": ...@@ -165,12 +165,12 @@ cdef extern from "check_size_smaller.h":
cpdef public int testme(Foo f) except -1: cpdef public int testme(Foo f) except -1:
return f.f9 return f.f9
######## check_size6.pyx ######## ######## check_size_invalid.pyx ########
cdef extern from "check_size_smaller.h": cdef extern from "check_size_smaller.h":
# Raise Compilerror when using bad value # Raise CompileError when using bad value
ctypedef class check_size.Foo [object FooStructSmall, check_size max]: ctypedef class check_size.Foo [object FooStructSmall, check_size hihi]:
cdef: cdef:
int f9 int f9
...@@ -180,20 +180,20 @@ cpdef public int testme(Foo f) except -1: ...@@ -180,20 +180,20 @@ cpdef public int testme(Foo f) except -1:
######## runner.py ######## ######## runner.py ########
import check_size, _check_size0, warnings import check_size, _check_size_exact, warnings
foo = check_size.Foo(23, 123, 1023) foo = check_size.Foo(23, 123, 1023)
assert foo.field0 == 23 assert foo.field0 == 23
assert foo.field1 == 123 assert foo.field1 == 123
ret = _check_size0.testme(foo) ret = _check_size_exact.testme(foo)
assert ret == 23 + 123 assert ret == 23 + 123
# ValueError since check_size.Foo's tp_basicsize is smaller than what is needed # ValueError since check_size.Foo's tp_basicsize is smaller than what is needed
# for FooStructBig. Messing with f2 will access memory outside the struct! # for FooStructBig. Messing with f2 will access memory outside the struct!
try: try:
import _check_size1 import _check_size_too_small
assert False assert False
except ValueError as e: except ValueError as e:
assert str(e).startswith('check_size.Foo size changed') assert str(e).startswith('check_size.Foo size changed')
...@@ -207,25 +207,28 @@ except ValueError as e: ...@@ -207,25 +207,28 @@ except ValueError as e:
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") warnings.simplefilter("always")
import _check_size2 import _check_size_default
import _check_size3 import _check_size_warn
assert len(w) == 2, 'expected two warnings, got %d' % len(w) assert len(w) == 2, 'expected two warnings, got %d' % len(w)
assert str(w[0].message).startswith('check_size.Foo size changed') assert str(w[0].message).startswith('check_size.Foo size changed')
assert str(w[1].message).startswith('check_size.Foo size changed') assert str(w[1].message).startswith('check_size.Foo size changed')
ret = _check_size2.testme(foo) ret = _check_size_default.testme(foo)
assert ret == 23 assert ret == 23
ret = _check_size3.testme(foo) ret = _check_size_warn.testme(foo)
assert ret == 23 assert ret == 23
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
# No warning, runtime vendor must provide backward compatibility # No warning, runtime vendor must provide backward compatibility
import _check_size4 import _check_size_extend
assert len(w) == 0 assert len(w) == 0
ret = _check_size_extend.testme(foo)
assert ret == 23
try: try:
# Enforce strict checking # Enforce strict checking
import _check_size5 import _check_size_error
assert False assert False
except ValueError as e: except ValueError as e:
assert str(e).startswith('check_size.Foo size changed') assert str(e).startswith('check_size.Foo size changed')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment