diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 16ea17d024f24023690003c39b8fd92a63203c13..68a6620df3afcdd48358b2eb570be47009c1e0b7 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -318,6 +318,8 @@ class ExprNode(Node): use_managed_ref = True # can be set by optimisation transforms result_is_used = True is_numpy_attribute = False + tracked_state = None + was_locked = True # The Analyse Expressions phase for expressions is split # into two sub-phases: @@ -692,25 +694,45 @@ class ExprNode(Node): error(self.pos, "Address is not constant") def set_autorlock(self, env): - self.entry.is_rlocked = True - self.entry.needs_rlock = True + self.tracked_state.was_locked = True + self.tracked_state.is_rlocked = True + self.tracked_state.needs_rlock = True def set_autowlock(self, env): - print "Setting wlock" - self.entry.is_wlocked = True - self.entry.needs_wlock = True + self.tracked_state.was_locked = True + self.tracked_state.is_wlocked = True + self.tracked_state.needs_wlock = True - def is_autolock(self, env): + def is_autolock(self): return self.type.is_cyp_class and self.type.lock_mode == "autolock" - def is_checklock(self, env): + def is_checklock(self): return self.type.is_cyp_class and self.type.lock_mode == "checklock" + def get_tracked_state(self, env): + if not hasattr(self, 'entry') or not self.entry.type.is_cyp_class: + return + self.tracked_state = env.lookup_tracked(self.entry) + if self.tracked_state is None: + self.tracked_state = env.declare_tracked(self.entry) + if self.is_autolock(): + env.declare_autolocked(self) + self.was_locked = self.tracked_state.was_locked + self.tracked_state.was_locked = True + def is_rhs_locked(self, env): - return not(hasattr(self, 'entry') and self.entry.type.is_cyp_class and not (self.entry.is_rlocked or self.entry.is_wlocked)) + if not hasattr(self, 'entry') or not self.entry.type.is_cyp_class: + # These nodes couldn't be tracked (because it is for example a constant), + # so we let them pass silently + return True + return self.tracked_state.is_rlocked or self.tracked_state.is_wlocked def is_lhs_locked(self, env): - return not(hasattr(self, 'entry') and self.entry.type.is_cyp_class and not self.entry.is_wlocked) + if not hasattr(self, 'entry') or not self.entry.type.is_cyp_class: + # These nodes couldn't be tracked (because it is for example a constant), + # so we let them pass silently + return True + return self.tracked_state.is_wlocked def ensure_subexpr_rhs_locked(self, env): for node in self.subexpr_nodes(): @@ -722,20 +744,24 @@ class ExprNode(Node): def ensure_rhs_locked(self, env, is_dereferenced = False): self.ensure_subexpr_rhs_locked(env) + if not self.tracked_state: + self.get_tracked_state(env) if is_dereferenced: if not self.is_rhs_locked(env): - if self.is_checklock(env): + if self.is_checklock(): error(self.pos, "This expression is not correctly locked (read lock needed)") - elif self.is_autolock(env): + elif self.is_autolock(): self.set_autorlock(env) def ensure_lhs_locked(self, env, is_dereferenced = False): self.ensure_subexpr_lhs_locked(env) + if not self.tracked_state: + self.get_tracked_state(env) if is_dereferenced: if not self.is_lhs_locked(env): - if self.is_checklock(env): + if self.is_checklock(): error(self.pos, "This expression is not correctly locked (write lock needed)") - elif self.is_autolock(env): + elif self.is_autolock(): self.set_autowlock(env) # ----------------- Result Allocation ----------------- @@ -2314,8 +2340,14 @@ class NameNode(AtomicExprNode): code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) - elif entry.is_local and entry.type.is_cyp_class: + elif entry.type.is_cyp_class: code.put_cygotref(self.result()) + if not self.was_locked and self.is_autolock(): + tracked_state = self.tracked_state + if tracked_state.needs_wlock: + code.putln("Cy_WLOCK(%s);" % self.result()) + elif tracked_state.needs_rlock: + code.putln("Cy_RLOCK(%s);" % self.result()) #pass # code.putln(entry.cname) elif entry.is_local or entry.in_closure or entry.from_closure or entry.type.is_memoryviewslice: @@ -2333,6 +2365,7 @@ class NameNode(AtomicExprNode): exception_check=None, exception_value=None): #print "NameNode.generate_assignment_code:", self.name ### entry = self.entry + tracked_state = self.tracked_state if entry is None: return # There was an error earlier @@ -2340,7 +2373,7 @@ class NameNode(AtomicExprNode): and not self.lhs_of_first_assignment and not rhs.in_module_scope): error(self.pos, "Literal list must be assigned to pointer at time of declaration") - if entry.needs_wlock or entry.needs_rlock: + if self.is_autolock() and tracked_state and (tracked_state.needs_wlock or tracked_state.needs_rlock): code.putln("Cy_UNLOCK(%s);" % self.result()) # is_pyglobal seems to be True for module level-globals only. @@ -2445,10 +2478,11 @@ class NameNode(AtomicExprNode): code.putln('new (&%s) decltype(%s){%s};' % (self.result(), self.result(), result)) elif result != self.result(): code.putln('%s = %s;' % (self.result(), result)) - if entry.needs_wlock: - code.putln("Cy_WLOCK(%s);" % self.result()) - elif entry.needs_rlock: - code.putln("Cy_RLOCK(%s);" % self.result()) + if self.is_autolock(): + if tracked_state.needs_wlock: + code.putln("Cy_WLOCK(%s);" % self.result()) + elif tracked_state.needs_rlock: + code.putln("Cy_RLOCK(%s);" % self.result()) if debug_disposal_code: print("NameNode.generate_assignment_code:") print("...generating post-assignment code for %s" % rhs) @@ -5827,10 +5861,11 @@ class SimpleCallNode(CallNode): for i in range(min(max_nargs, actual_nargs)): formal_arg = func_type.args[i] actual_arg = args[i] - if formal_arg.type.is_const: - actual_arg.ensure_rhs_locked(env, is_dereferenced = True) - else: - actual_arg.ensure_lhs_locked(env, is_dereferenced = True) + if formal_arg.type.is_cyp_class: + if formal_arg.type.is_const: + actual_arg.ensure_rhs_locked(env, is_dereferenced = True) + else: + actual_arg.ensure_lhs_locked(env, is_dereferenced = True) # Coerce arguments some_args_in_temps = False for i in range(min(max_nargs, actual_nargs)): @@ -7369,6 +7404,12 @@ class AttributeNode(ExprNode): '"Memoryview is not initialized");' '%s' '}' % (self.result(), code.error_goto(self.pos))) + elif self.is_autolock(): + if not self.was_locked: + if self.tracked_state.needs_wlock: + code.putln("Cy_WLOCK(%s);" % self.result()) + elif self.tracked_state.needs_rlock: + code.putln("Cy_RLOCK(%s);" % self.result()) else: # result_code contains what is needed, but we may need to insert # a check and raise an exception @@ -7408,7 +7449,9 @@ class AttributeNode(ExprNode): rhs.result_as(self.ctype()))) else: select_code = self.result() - if self.entry.needs_rlock or self.entry.needs_wlock: + # XXX - Greater to have a getter, right ? + tracked_state = self.tracked_state + if self.is_autolock() and tracked_state and (tracked_state.needs_rlock or tracked_state.needs_wlock): code.putln("Cy_UNLOCK(%s);" % select_code) if self.type.is_pyobject and self.use_managed_ref: rhs.make_owned_reference(code) @@ -7431,10 +7474,11 @@ class AttributeNode(ExprNode): select_code, rhs.result_as(self.ctype()))) #rhs.result())) - if self.entry.needs_wlock: - code.putln("Cy_WLOCK(%s);" % select_code) - elif self.entry.needs_rlock: - code.putln("Cy_RLOCK(%s);" % select_code) + if self.is_autolock(): + if tracked_state.needs_wlock: + code.putln("Cy_WLOCK(%s);" % select_code) + elif tracked_state.needs_rlock: + code.putln("Cy_RLOCK(%s);" % select_code) rhs.generate_post_assignment_code(code) rhs.free_temps(code) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 4c5ad999a7a0f44a5f849e2078bcd6f9c4d3fcd9..48c170a8336f2cbea307d5cf245c9e64fe753042 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -2168,6 +2168,23 @@ class FuncDefNode(StatNode, BlockNode): # ----- Return cleanup for both error and no-error return code.put_label(code.return_from_error_cleanup_label) + for node in reversed(lenv.autolocked_nodes): + # We iterate in the reverse order to properly unlock + # nested locked objects (aka most nested first). + + # For example, if we have the following situation: + # obj.sub_obj.attr = some_value + # If obj and sub_obj are both of autolocked types, + # the obj (name)node is declared before the sub_obj (attribute)node. + # If we unlock first obj, another thread could immediately acquire + # a write lock and change where sub_obj points to. + # We would then try to unlock the new sub_obj reference, + # which leads to a dangling lock on the previous reference + # (and attempt to unlock a non-locked ref). + + if not node.was_locked and (node.tracked_state.needs_wlock or node.tracked_state.needs_rlock): + code.putln("Cy_UNLOCK(%s);" % node.result()) + for entry in lenv.var_entries: if not entry.used or entry.in_closure: continue @@ -2186,10 +2203,6 @@ class FuncDefNode(StatNode, BlockNode): # NULL as a valid cypclass (with a typecast) code.put_cyxdecref(entry.cname) - for node in lenv.autolocked_nodes: - if node.entry.needs_rlock or node.entry.needs_wlock: - code.putln("Cy_UNLOCK(%s);" % node.result()) - # Decref any increfed args for entry in lenv.arg_entries: if entry.type.is_pyobject: @@ -2585,7 +2598,8 @@ class CFuncDefNode(FuncDefNode): entry = self.local_scope.declare(_name, _cname, _type, _pos, 'private') entry.is_variable = 1 # Even if it is checklock it should be OK to mess with self without locking - entry.is_wlocked = True + self_locking_state = self.local_scope.declare_tracked(entry) + self_locking_state.is_wlocked = True def declare_cpdef_wrapper(self, env): if self.overridable: @@ -5550,11 +5564,6 @@ class SingleAssignmentNode(AssignmentNode): self.lhs = self.lhs.analyse_target_types(env) self.lhs.gil_assignment_check(env) - if hasattr(self.lhs, 'entry'): - entry = self.lhs.entry - if entry.type.is_cyp_class and entry.type.lock_mode == "autolock"\ - and not (entry.needs_rlock or entry.needs_wlock): - env.declare_autolocked(self.lhs) self.rhs.ensure_rhs_locked(env) self.lhs.ensure_lhs_locked(env) unrolled_assignment = self.unroll_lhs(env) @@ -8186,13 +8195,16 @@ class LockCypclassNode(StatNode): def analyse_expressions(self, env): self.obj = self.obj.analyse_types(env) + self.obj.ensure_rhs_locked(env) if not hasattr(self.obj, 'entry'): error(self.pos, "The (un)locking target has no entry") if not self.obj.type.is_cyp_class: error(self.pos, "Cannot (un)lock a non-cypclass variable !") - is_rlocked = self.obj.entry.is_rlocked - is_wlocked = self.obj.entry.is_wlocked + # FIXME: this is a bit redundant here + self.obj.get_tracked_state(env) + is_rlocked = self.obj.is_rhs_locked(env) + is_wlocked = self.obj.is_lhs_locked(env) if self.state == "unclocked" and not (is_rlocked or is_wlocked): error(self.pos, "Cannot unlock an already unlocked object !") @@ -8209,23 +8221,29 @@ class LockCypclassNode(StatNode): self.was_rlocked = is_rlocked self.was_wlocked = is_wlocked + tracked_state = self.obj.tracked_state + if self.state == "rlocked": - self.obj.entry.is_rlocked = True - self.obj.entry.is_wlocked = False + tracked_state.is_rlocked = True + tracked_state.is_wlocked = False elif self.state == "wlocked": - self.obj.entry.is_rlocked = False - self.obj.entry.is_wlocked = True + tracked_state.is_rlocked = False + tracked_state.is_wlocked = True else: - self.obj.entry.is_rlocked = False - self.obj.entry.is_wlocked = False + tracked_state.is_rlocked = False + tracked_state.is_wlocked = False self.body = self.body.analyse_expressions(env) - self.obj.entry.is_rlocked = self.was_rlocked - self.obj.entry.is_wlocked = self.was_wlocked + #self.obj.entry.is_rlocked = self.was_rlocked + #self.obj.entry.is_wlocked = self.was_wlocked + + tracked_state.is_rlocked = self.was_rlocked + tracked_state.is_wlocked = self.was_wlocked return self def generate_execution_code(self, code): + self.obj.generate_evaluation_code(code) # We must unlock if it's a 'with unlocked' statement, # or if we're changing lock type. if self.was_rlocked or self.was_wlocked: diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 91ec8b6b763d3b1c05224b9234bad737a618698c..fa24128142b43a7452786d14dd40a05a3f6c92e7 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -139,6 +139,7 @@ class Entry(object): # is_rlocked boolean Is locked with a read lock (used for cypclass) # needs_rlock boolean The entry needs a read lock (used in autolock mode) # needs_wlock boolean The entry needs a write lock (used in autolock mode) + # was_locked boolean Indicates to nodes falling through that the first lock already took place # TODO: utility_code and utility_code_definition serves the same purpose... @@ -213,6 +214,7 @@ class Entry(object): is_rlocked = False needs_rlock = False needs_wlock = False + was_locked = False def __init__(self, name, cname, type, pos = None, init = None): self.name = name @@ -283,6 +285,15 @@ class InnerEntry(Entry): def all_entries(self): return self.defining_entry.all_entries() +class TrackedLockedEntry: + def __init__(self, entry, scope): + self.entry = entry + self.scope = scope + self.is_wlocked = False + self.is_rlocked = False + self.needs_wlock = False + self.needs_rlock = False + self.was_locked = False class Scope(object): # name string Unqualified name @@ -365,6 +376,8 @@ class Scope(object): self.buffer_entries = [] self.lambda_defs = [] self.id_counters = {} + self.tracked_entries = {} + def __deepcopy__(self, memo): return self @@ -447,6 +460,18 @@ class Scope(object): for scope in sorted(self.subscopes, key=operator.attrgetter('scope_prefix')): yield scope + def declare_tracked(self, entry): + # Keying only with the name is wrong: if we have multiple attributes + # with the same name in different cypclass, this will conflict. + key = entry + self.tracked_entries[key] = TrackedLockedEntry(entry, self) + return self.tracked_entries[key] + + def lookup_tracked(self, entry): + # We don't chain up the scopes on purpose: we want to keep things local + key = entry + return self.tracked_entries.get(key, None) + def declare(self, name, cname, type, pos, visibility, shadow = 0, is_type = 0, create_wrapper = 0): # Create new entry, and add to dictionary if # name is not None. Reports a warning if already @@ -1799,7 +1824,8 @@ class LocalScope(Scope): entry.init = "0" entry.is_arg = 1 if type.is_cyp_class and type.lock_mode == "autolock": - entry.is_wlocked = True + arg_lock_state = self.declare_tracked(entry) + arg_lock_state.is_wlocked = True #entry.borrowed = 1 # Not using borrowed arg refs for now self.arg_entries.append(entry) return entry