From 0ed4800a2dd9ccf81cba449d4dc600ecef309de4 Mon Sep 17 00:00:00 2001 From: Dag Sverre Seljebotn Date: Thu, 21 Aug 2008 15:24:38 +0200 Subject: [PATCH] Prototype for None-checking. *Very* unstable. --- Cython/Compiler/ExprNodes.py | 23 +++++++ Cython/Compiler/Main.py | 3 + Cython/Compiler/Optimize.py | 116 +++++++++++++++++++++++++++++++++ tests/run/noneattributeacc.pyx | 52 +++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 tests/run/noneattributeacc.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 253ff749..960db516 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -830,10 +830,12 @@ class NameNode(AtomicExprNode): # # entry Entry Symbol table entry # interned_cname string + # possible_var_values object See Optimize.FindPossibleVariableValues is_name = 1 skip_assignment_decref = False entry = None + possible_var_values = None def create_analysed_rvalue(pos, env, entry): node = NameNode(pos) @@ -2073,6 +2075,7 @@ class AttributeNode(ExprNode): # # obj ExprNode # attribute string + # needs_none_check boolean Used if obj is an extension type. # # Used internally: # @@ -2089,6 +2092,7 @@ class AttributeNode(ExprNode): result = "" entry = None is_called = 0 + needs_none_check = True def coerce_to(self, dst_type, env): # If coercing to a generic pyobject and this is a cpdef function @@ -2317,6 +2321,15 @@ class AttributeNode(ExprNode): self.obj.py_result(), self.interned_attr_cname, code.error_goto_if_null(self.result_code, self.pos))) + else: + # result_code contains what is needed, but we may need to insert + # a check and raise an exception + if self.obj.type.is_extension_type and self.needs_none_check: + code.globalstate.use_utility_code(raise_noneattr_error_utility_code) + code.putln("if (%s) {" % code.unlikely("%s == Py_None") % self.obj.result_as(PyrexTypes.py_object_type)) + code.putln("__Pyx_RaiseNoneAttributeError(\"%s\");" % self.attribute.encode("UTF-8")) # todo: fix encoding + code.putln(code.error_goto(self.pos)) + code.putln("}") def generate_assignment_code(self, rhs, code): self.obj.generate_evaluation_code(code) @@ -4551,3 +4564,13 @@ static INLINE int __Pyx_SetItemInt(PyObject *o, Py_ssize_t i, PyObject *v, int i """, """ """] +#------------------------------------------------------------------------------------ + +raise_noneattr_error_utility_code = [ +""" +static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname); +""", """ +static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname) { + PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%s'", attrname); +} +"""] diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py index 8a9139d0..67216abd 100644 --- a/Cython/Compiler/Main.py +++ b/Cython/Compiler/Main.py @@ -81,6 +81,7 @@ class Context: from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform from ParseTreeTransforms import ResolveOptions from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting + from Optimize import OptimizeNoneChecking, FindPossibleVariableValues from Buffer import IntroduceBufferAuxiliaryVars from ModuleNode import check_c_classes @@ -104,7 +105,9 @@ class Context: _check_c_classes, AnalyseExpressionsTransform(self), SwitchTransform(), + FindPossibleVariableValues(self), OptimizeRefcounting(self), + OptimizeNoneChecking(self), # SpecialFunctions(self), # CreateClosureClasses(context), ] diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index 6036a5ad..8c19c126 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -149,3 +149,119 @@ class OptimizeRefcounting(Visitor.CythonTransform): # Set a flag in NameNode to skip the decref lhs.skip_assignment_decref = True return node + + +class ExtTypePossibleValues: + can_be_none = True + def copy_with(self, can_be_none=None): + result = ExtTypePossibleValues() + if can_be_none is not None: + result.can_be_none = can_be_none + return result + def new(self): + "Polymorphic constructor" + return ExtTypePossibleValues() + +class FindPossibleVariableValues(Visitor.CythonTransform): + """ + Annotates NameNodes with information about the possible values + the variable referred to can take, *at that point* in the execution. + + This is done on a best effort basis, so we can be as smart or dumb + as we want. A do-nothing-op should always be valid. + + Each type of variable keeps a different type of "variable range" + information. + + This information is invalid if the tree is reorganized (read: + keep this transform late in the pipeline). + + Currently this is done: + - Extension types gets flagged + """ + + # + # Manage info stack + # + def create_empty_knowledge(self, scope): + knowledge = {} + for entry in scope.entries.values(): + if entry.type.is_extension_type: + knowledge[entry] = ExtTypePossibleValues() + return knowledge + + def visit_ModuleNode(self, node): + self.knowledge = self.create_empty_knowledge(node.scope) + self.visitchildren(node) + return node + + def visit_FuncDefNode(self, node): + oldknow = self.knowledge + self.knowledge = self.create_empty_knowledge(node.local_scope) + self.visitchildren(node) + self.knowledge = oldknow + return node + + def visit_NameNode(self, node): + node.possible_var_values = self.knowledge.get(node.entry, None) + return node + + # + # Conditions which restrict possible variable values + # + def visit_IfClauseNode(self, clause): + def process(): + self.visitchildren(clause) + return clause + + # we're lazy and only check in one specific easy case: single comparison with None + # the code is a bit nasty but handling the proper cases will force through better code + # anyway + cond = clause.condition + if not isinstance(cond, ExprNodes.PrimaryCmpNode): return process() + if clause.condition.cascade is not None: return process() + if isinstance(cond.operand1, ExprNodes.NoneNode): + operand_checked = cond.operand2 + elif isinstance(cond.operand2, ExprNodes.NoneNode): + operand_checked = cond.operand1 + else: + return process() + if not isinstance(operand_checked, ExprNodes.NameNode): + return process() + entry = operand_checked.entry + if entry not in self.knowledge: + # Not tracking this variable + return process() + # Finally! + if cond.operator == 'is_not': + # Within this block we can assume the variable is not None + # (until it is reassigned) + self.visitchildren(clause, attrs=["condition"]) + oldvalues = self.knowledge[entry] + self.knowledge[entry] = oldvalues.copy_with(can_be_none=False) + self.visitchildren(clause, attrs=["body"]) + self.knowledge[entry] = oldvalues + return clause + else: + return process() + + + # Assignments which reset possible variable values + def visit_SingleAssignmentNode(self, node): + if isinstance(node.lhs, ExprNodes.NameNode): + entry = node.lhs.entry + if entry in self.knowledge: + self.knowledge[entry] = self.knowledge[entry].new() + self.visitchildren(node) + return node + +class OptimizeNoneChecking(Visitor.CythonTransform): + def visit_AttributeNode(self, node): + if isinstance(node.obj, ExprNodes.NameNode): + obj = node.obj + if obj.type.is_extension_type and not obj.possible_var_values.can_be_none: + node.needs_none_check = False + self.visitchildren(node) + return node + + diff --git a/tests/run/noneattributeacc.pyx b/tests/run/noneattributeacc.pyx new file mode 100644 index 00000000..4a885d61 --- /dev/null +++ b/tests/run/noneattributeacc.pyx @@ -0,0 +1,52 @@ +""" +Tests accessing attributes of extension type variables +set to None + +>>> obj = MyClass(2, 3) +>>> func(obj) +2 +>>> func(None) +Traceback (most recent call last): + ... +AttributeError: 'NoneType' object has no attribute 'a' + +>>> checking(obj) +2 +2 +>>> checking(None) +var is None + +>>> check_and_assign(obj) +Traceback (most recent call last): + ... +AttributeError: 'NoneType' object has no attribute 'a' + +""" + +cdef class MyClass: + cdef int a, b + def __init__(self, a, b): + self.a = a + self.b = b + +def func(MyClass var): + print var.a + +def some(): + return MyClass(4, 5) + +def checking(MyClass var): + state = (var is None) + if not state: + print var.a + if var is not None: + print var.a + else: + print "var is None" + +def check_and_assign(MyClass var): + if var is not None: + print var.a + var = None + print var.a + -- 2.26.2