From 64908ab7065a9d43ad2f72609b0dcebd2fb8d8bd Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 29 Mar 2009 20:55:51 +0200 Subject: [PATCH] NoneCheckNode to enforce runtime None checks for object references --- Cython/Compiler/ExprNodes.py | 39 +++++++++++++++++++++++++++++++++++- Cython/Compiler/Optimize.py | 39 +++++++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index f464e9fe..ead7f838 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -5091,7 +5091,44 @@ class PyTypeTestNode(CoercionNode): def free_temps(self, code): self.arg.free_temps(code) - + + +class NoneCheckNode(CoercionNode): + # This node is used to check that a Python object is not None and + # raises an appropriate exception (as specified by the creating + # transform). + + def __init__(self, arg, exception_type_cname, exception_message): + CoercionNode.__init__(self, arg) + self.type = arg.type + self.result_ctype = arg.ctype() + self.exception_type_cname = exception_type_cname + self.exception_message = exception_message + + def analyse_types(self, env): + pass + + def result_in_temp(self): + return self.arg.result_in_temp() + + def calculate_result_code(self): + return self.arg.result() + + def generate_result_code(self, code): + code.putln( + "if (unlikely(%s == Py_None)) {" % self.arg.result()) + code.putln('PyErr_SetString(%s, "%s"); %s ' % ( + self.exception_type_cname, + StringEncoding.escape_byte_string(self.exception_message), + code.error_goto(self.pos))) + code.putln("}") + + def generate_post_assignment_code(self, code): + self.arg.generate_post_assignment_code(code) + + def free_temps(self, code): + self.arg.free_temps(code) + class CoerceToPyTypeNode(CoercionNode): # This node is used to convert a C data type diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index 7f4972a5..ebbf497b 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -476,6 +476,7 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): arg_list = arg_tuple.args self_arg = function.obj obj_type = self_arg.type + is_unbound_method = False if obj_type.is_builtin_type: if obj_type is Builtin.type_type and arg_list and \ arg_list[0].type.is_pyobject: @@ -483,6 +484,7 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): # (ignoring 'type.mro()' here ...) type_name = function.obj.name self_arg = None + is_unbound_method = True else: type_name = obj_type.name else: @@ -494,9 +496,9 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): if self_arg is not None: arg_list = [self_arg] + list(arg_list) if kwargs: - return method_handler(node, arg_list, kwargs) + return method_handler(node, arg_list, kwargs, is_unbound_method) else: - return method_handler(node, arg_list) + return method_handler(node, arg_list, is_unbound_method) else: return node @@ -625,7 +627,7 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None), ]) - def _handle_simple_method_object_append(self, node, args): + def _handle_simple_method_object_append(self, node, args, is_unbound_method): # X.append() is almost always referring to a list if len(args) != 2: return node @@ -644,13 +646,14 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): ], exception_value = "-1") - def _handle_simple_method_list_append(self, node, args): + def _handle_simple_method_list_append(self, node, args, is_unbound_method): if len(args) != 2: error(node.pos, "list.append(x) called with wrong number of args, found %d" % len(args)) return node return self._substitute_method_call( - node, "PyList_Append", self.PyList_Append_func_type, args) + node, "PyList_Append", self.PyList_Append_func_type, + 'append', is_unbound_method, args) single_param_func_type = PyrexTypes.CFuncType( PyrexTypes.c_int_type, [ @@ -658,21 +661,37 @@ class OptimizeBuiltinCalls(Visitor.VisitorTransform): ], exception_value = "-1") - def _handle_simple_method_list_sort(self, node, args): + def _handle_simple_method_list_sort(self, node, args, is_unbound_method): if len(args) != 1: return node return self._substitute_method_call( - node, "PyList_Sort", self.single_param_func_type, args) + node, "PyList_Sort", self.single_param_func_type, + 'sort', is_unbound_method, args) - def _handle_simple_method_list_reverse(self, node, args): + def _handle_simple_method_list_reverse(self, node, args, is_unbound_method): if len(args) != 1: error(node.pos, "list.reverse(x) called with wrong number of args, found %d" % len(args)) + return node return self._substitute_method_call( - node, "PyList_Reverse", self.single_param_func_type, args) + node, "PyList_Reverse", self.single_param_func_type, + 'reverse', is_unbound_method, args) - def _substitute_method_call(self, node, name, func_type, args=()): + def _substitute_method_call(self, node, name, func_type, + attr_name, is_unbound_method, args=()): args = list(args) + if args: + self_arg = args[0] + if is_unbound_method: + self_arg = ExprNodes.NoneCheckNode( + self_arg, "PyExc_TypeError", + "descriptor '%s' requires a '%s' object but received a 'NoneType'" % ( + attr_name, node.function.obj.name)) + else: + self_arg = ExprNodes.NoneCheckNode( + self_arg, "PyExc_AttributeError", + "'NoneType' object has no attribute '%s'" % attr_name) + args[0] = self_arg # FIXME: args[0] may need a runtime None check (ticket #166) return ExprNodes.PythonCapiCallNode( node.pos, name, func_type, -- 2.26.2