Commit 6c41a13e authored by scoder's avatar scoder

Merge pull request #36 from vitek/_control_flow

Merged in Vitja's control flow analysis implementation.
parents 613a4861 970dc0d6
...@@ -1140,7 +1140,7 @@ static CYTHON_INLINE void __Pyx_ZeroBuffer(Py_buffer* buf) { ...@@ -1140,7 +1140,7 @@ static CYTHON_INLINE void __Pyx_ZeroBuffer(Py_buffer* buf) {
} }
static CYTHON_INLINE int __Pyx_GetBufferAndValidate(Py_buffer* buf, PyObject* obj, __Pyx_TypeInfo* dtype, int flags, int nd, int cast, __Pyx_BufFmt_StackElem* stack) { static CYTHON_INLINE int __Pyx_GetBufferAndValidate(Py_buffer* buf, PyObject* obj, __Pyx_TypeInfo* dtype, int flags, int nd, int cast, __Pyx_BufFmt_StackElem* stack) {
if (obj == Py_None) { if (obj == Py_None || obj == NULL) {
__Pyx_ZeroBuffer(buf); __Pyx_ZeroBuffer(buf);
return 0; return 0;
} }
......
...@@ -39,6 +39,7 @@ Options: ...@@ -39,6 +39,7 @@ Options:
-3 Compile based on Python-3 syntax and code semantics. -3 Compile based on Python-3 syntax and code semantics.
--fast-fail Abort the compilation on the first error --fast-fail Abort the compilation on the first error
--warning-error, -Werror Make all warnings into errors --warning-error, -Werror Make all warnings into errors
--warning-extra, -Wextra Enable extra warnings
-X, --directive <name>=<value>[,<name=value,...] Overrides a compiler directive -X, --directive <name>=<value>[,<name=value,...] Overrides a compiler directive
""" """
...@@ -132,6 +133,8 @@ def parse_command_line(args): ...@@ -132,6 +133,8 @@ def parse_command_line(args):
Options.fast_fail = True Options.fast_fail = True
elif option in ('-Werror', '--warning-errors'): elif option in ('-Werror', '--warning-errors'):
Options.warning_errors = True Options.warning_errors = True
elif option in ('-Wextra', '--warning-extra'):
options.compiler_directives.update(Options.extra_warnings)
elif option == "--disable-function-redefinition": elif option == "--disable-function-redefinition":
Options.disable_function_redefinition = True Options.disable_function_redefinition = True
elif option == "--directive" or option.startswith('-X'): elif option == "--directive" or option.startswith('-X'):
......
...@@ -1187,6 +1187,8 @@ class CCodeWriter(object): ...@@ -1187,6 +1187,8 @@ class CCodeWriter(object):
entry.cname, dll_linkage = dll_linkage)) entry.cname, dll_linkage = dll_linkage))
if entry.init is not None: if entry.init is not None:
self.put_safe(" = %s" % entry.type.literal_code(entry.init)) self.put_safe(" = %s" % entry.type.literal_code(entry.init))
elif entry.type.is_pyobject:
self.put(" = NULL");
self.putln(";") self.putln(";")
def put_temp_declarations(self, func_context): def put_temp_declarations(self, func_context):
...@@ -1290,10 +1292,7 @@ class CCodeWriter(object): ...@@ -1290,10 +1292,7 @@ class CCodeWriter(object):
def put_var_decref(self, entry): def put_var_decref(self, entry):
if entry.type.is_pyobject: if entry.type.is_pyobject:
if entry.init_to_none is False: # FIXME: 0 and False are treated differently??? self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry))
self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry))
else:
self.putln("__Pyx_DECREF(%s);" % self.entry_as_pyobject(entry))
def put_var_decref_clear(self, entry): def put_var_decref_clear(self, entry):
if entry.type.is_pyobject: if entry.type.is_pyobject:
...@@ -1420,6 +1419,19 @@ class CCodeWriter(object): ...@@ -1420,6 +1419,19 @@ class CCodeWriter(object):
# return self.putln("if (unlikely(%s < 0)) %s" % (value, self.error_goto(pos))) # TODO this path is almost _never_ taken, yet this macro makes is slower! # return self.putln("if (unlikely(%s < 0)) %s" % (value, self.error_goto(pos))) # TODO this path is almost _never_ taken, yet this macro makes is slower!
return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos))) return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos)))
def put_error_if_unbound(self, pos, entry):
import ExprNodes
if entry.from_closure:
func = '__Pyx_RaiseClosureNameError'
self.globalstate.use_utility_code(
ExprNodes.raise_closure_name_error_utility_code)
else:
func = '__Pyx_RaiseUnboundLocalError'
self.globalstate.use_utility_code(
ExprNodes.raise_unbound_local_error_utility_code)
self.put('if (unlikely(!%s)) { %s("%s"); %s }' % (
entry.cname, func, entry.name, self.error_goto(pos)))
def set_error_info(self, pos): def set_error_info(self, pos):
self.funcstate.should_declare_error_indicator = True self.funcstate.should_declare_error_indicator = True
if self.c_line_in_traceback: if self.c_line_in_traceback:
......
...@@ -1267,14 +1267,20 @@ class NameNode(AtomicExprNode): ...@@ -1267,14 +1267,20 @@ class NameNode(AtomicExprNode):
# name string Python name of the variable # name string Python name of the variable
# entry Entry Symbol table entry # entry Entry Symbol table entry
# type_entry Entry For extension type names, the original type entry # type_entry Entry For extension type names, the original type entry
# cf_is_null boolean Is uninitialized before this node
# cf_maybe_null boolean Maybe uninitialized before this node
# allow_null boolean Don't raise UnboundLocalError
is_name = True is_name = True
is_cython_module = False is_cython_module = False
cython_attribute = None cython_attribute = None
lhs_of_first_assignment = False lhs_of_first_assignment = False # TODO: remove me
is_used_as_rvalue = 0 is_used_as_rvalue = 0
entry = None entry = None
type_entry = None type_entry = None
cf_maybe_null = True
cf_is_null = False
allow_null = False
def create_analysed_rvalue(pos, env, entry): def create_analysed_rvalue(pos, env, entry):
node = NameNode(pos) node = NameNode(pos)
...@@ -1550,15 +1556,11 @@ class NameNode(AtomicExprNode): ...@@ -1550,15 +1556,11 @@ class NameNode(AtomicExprNode):
code.error_goto_if_null(self.result(), self.pos))) code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result()) code.put_gotref(self.py_result())
elif entry.is_local and False: elif entry.is_local or entry.in_closure or entry.from_closure:
# control flow not good enough yet if entry.type.is_pyobject:
assigned = entry.scope.control_flow.get_state((entry.name, 'initialized'), self.pos) if (self.cf_maybe_null or self.cf_is_null) \
if assigned is False: and not self.allow_null:
error(self.pos, "local variable '%s' referenced before assignment" % entry.name) code.put_error_if_unbound(self.pos, entry)
elif not Options.init_local_none and assigned is None:
code.putln('if (%s == 0) { PyErr_SetString(PyExc_UnboundLocalError, "%s"); %s }' %
(entry.cname, entry.name, code.error_goto(self.pos)))
entry.scope.control_flow.set_state(self.pos, (entry.name, 'initialized'), True)
def generate_assignment_code(self, rhs, code): def generate_assignment_code(self, rhs, code):
#print "NameNode.generate_assignment_code:", self.name ### #print "NameNode.generate_assignment_code:", self.name ###
...@@ -1627,17 +1629,20 @@ class NameNode(AtomicExprNode): ...@@ -1627,17 +1629,20 @@ class NameNode(AtomicExprNode):
if self.use_managed_ref: if self.use_managed_ref:
rhs.make_owned_reference(code) rhs.make_owned_reference(code)
is_external_ref = entry.is_cglobal or self.entry.in_closure or self.entry.from_closure is_external_ref = entry.is_cglobal or self.entry.in_closure or self.entry.from_closure
if not self.lhs_of_first_assignment: if is_external_ref:
if is_external_ref: if not self.cf_is_null:
code.put_gotref(self.py_result()) if self.cf_maybe_null:
if entry.is_local and not Options.init_local_none: code.put_xgotref(self.py_result())
initialized = entry.scope.control_flow.get_state((entry.name, 'initialized'), self.pos) else:
if initialized is True: code.put_gotref(self.py_result())
code.put_decref(self.result(), self.ctype()) if entry.is_cglobal:
elif initialized is None: code.put_decref(self.result(), self.ctype())
else:
if not self.cf_is_null:
if self.cf_maybe_null:
code.put_xdecref(self.result(), self.ctype()) code.put_xdecref(self.result(), self.ctype())
else: else:
code.put_decref(self.result(), self.ctype()) code.put_decref(self.result(), self.ctype())
if is_external_ref: if is_external_ref:
code.put_giveref(rhs.py_result()) code.put_giveref(rhs.py_result())
...@@ -1686,8 +1691,11 @@ class NameNode(AtomicExprNode): ...@@ -1686,8 +1691,11 @@ class NameNode(AtomicExprNode):
Naming.module_cname, Naming.module_cname,
self.entry.name)) self.entry.name))
elif self.entry.type.is_pyobject: elif self.entry.type.is_pyobject:
# Fake it until we can do it for real... if not self.cf_is_null:
self.generate_assignment_code(NoneNode(self.pos), code) if self.cf_maybe_null:
code.put_error_if_unbound(self.pos, self.entry)
code.put_decref(self.result(), self.ctype())
code.putln('%s = NULL;' % self.result())
else: else:
error(self.pos, "Deletion of C names not supported") error(self.pos, "Deletion of C names not supported")
...@@ -4485,8 +4493,6 @@ class ScopedExprNode(ExprNode): ...@@ -4485,8 +4493,6 @@ class ScopedExprNode(ExprNode):
generate_inner_evaluation_code(code) generate_inner_evaluation_code(code)
code.putln('} /* exit inner scope */') code.putln('} /* exit inner scope */')
return return
for entry in py_entries:
code.put_init_var_to_py_none(entry)
# must free all local Python references at each exit point # must free all local Python references at each exit point
old_loop_labels = tuple(code.new_loop_labels()) old_loop_labels = tuple(code.new_loop_labels())
...@@ -4731,12 +4737,14 @@ class SetNode(ExprNode): ...@@ -4731,12 +4737,14 @@ class SetNode(ExprNode):
class DictNode(ExprNode): class DictNode(ExprNode):
# Dictionary constructor. # Dictionary constructor.
# #
# key_value_pairs [DictItemNode] # key_value_pairs [DictItemNode]
# exclude_null_values [boolean] Do not add NULL values to dict
# #
# obj_conversion_errors [PyrexError] used internally # obj_conversion_errors [PyrexError] used internally
subexprs = ['key_value_pairs'] subexprs = ['key_value_pairs']
is_temp = 1 is_temp = 1
exclude_null_values = False
type = dict_type type = dict_type
obj_conversion_errors = [] obj_conversion_errors = []
...@@ -4824,11 +4832,15 @@ class DictNode(ExprNode): ...@@ -4824,11 +4832,15 @@ class DictNode(ExprNode):
for item in self.key_value_pairs: for item in self.key_value_pairs:
item.generate_evaluation_code(code) item.generate_evaluation_code(code)
if self.type.is_pyobject: if self.type.is_pyobject:
if self.exclude_null_values:
code.putln('if (%s) {' % item.value.py_result())
code.put_error_if_neg(self.pos, code.put_error_if_neg(self.pos,
"PyDict_SetItem(%s, %s, %s)" % ( "PyDict_SetItem(%s, %s, %s)" % (
self.result(), self.result(),
item.key.py_result(), item.key.py_result(),
item.value.py_result())) item.value.py_result()))
if self.exclude_null_values:
code.putln('}')
else: else:
code.putln("%s.%s = %s;" % ( code.putln("%s.%s = %s;" % (
self.result(), self.result(),
...@@ -8280,6 +8292,26 @@ static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void) { ...@@ -8280,6 +8292,26 @@ static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void) {
} }
''') ''')
raise_unbound_local_error_utility_code = UtilityCode(
proto = """
static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname);
""",
impl = """
static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname) {
PyErr_Format(PyExc_UnboundLocalError, "local variable '%s' referenced before assignment", varname);
}
""")
raise_closure_name_error_utility_code = UtilityCode(
proto = """
static CYTHON_INLINE void __Pyx_RaiseClosureNameError(const char *varname);
""",
impl = """
static CYTHON_INLINE void __Pyx_RaiseClosureNameError(const char *varname) {
PyErr_Format(PyExc_NameError, "free variable '%s' referenced before assignment in enclosing scope", varname);
}
""")
#------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------
getitem_dict_utility_code = UtilityCode( getitem_dict_utility_code = UtilityCode(
......
cimport cython
cdef class ControlBlock:
cdef public set children
cdef public set parents
cdef public set positions
cdef public list stats
cdef public dict gen
cdef public set bounded
cdef public dict input
cdef public dict output
# Big integer it bitsets
cdef public object i_input
cdef public object i_output
cdef public object i_gen
cdef public object i_kill
cdef public object i_state
cpdef bint empty(self)
cpdef detach(self)
cpdef add_child(self, block)
cdef class ExitBlock(ControlBlock):
cpdef bint empty(self)
cdef class NameAssignment:
cdef public bint is_arg
cdef public object lhs
cdef public object rhs
cdef public object entry
cdef public object pos
cdef public set refs
cdef public object bit
cdef class AssignmentList:
cdef public object bit
cdef public object mask
cdef public list stats
cdef class ControlFlow:
cdef public set blocks
cdef public set entries
cdef public list loops
cdef public list exceptions
cdef public ControlBlock entry_point
cdef public ExitBlock exit_point
cdef public ControlBlock block
cdef public dict assmts
cpdef newblock(self, parent=*)
cpdef nextblock(self, parent=*)
cpdef bint is_tracked(self, entry)
cpdef mark_position(self, node)
cpdef mark_assignment(self, lhs, rhs, entry=*)
cpdef mark_argument(self, lhs, rhs, entry)
cpdef mark_deletion(self, node, entry)
cpdef mark_reference(self, node, entry)
cpdef normalize(self)
@cython.locals(offset=object, assmts=AssignmentList,
block=ControlBlock)
cpdef initialize(self)
@cython.locals(assmts=AssignmentList, assmt=NameAssignment)
cpdef set map_one(self, istate, entry)
@cython.locals(block=ControlBlock, parent=ControlBlock)
cdef reaching_definitions(self)
cdef class Uninitialized:
pass
@cython.locals(dirty=bint, block=ControlBlock, parent=ControlBlock)
cdef check_definitions(ControlFlow flow, dict compiler_directives)
This diff is collapsed.
...@@ -110,6 +110,7 @@ class Context(object): ...@@ -110,6 +110,7 @@ class Context(object):
from TypeInference import MarkAssignments, MarkOverflowingArithmetic from TypeInference import MarkAssignments, MarkOverflowingArithmetic
from ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions from ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
from ParseTreeTransforms import RemoveUnreachableCode, GilCheck from ParseTreeTransforms import RemoveUnreachableCode, GilCheck
from FlowControl import CreateControlFlowGraph
from AnalysedTreeTransforms import AutoTestDictTransform from AnalysedTreeTransforms import AutoTestDictTransform
from AutoDocTransforms import EmbedSignature from AutoDocTransforms import EmbedSignature
from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
...@@ -149,9 +150,11 @@ class Context(object): ...@@ -149,9 +150,11 @@ class Context(object):
AutoTestDictTransform(self), AutoTestDictTransform(self),
EmbedSignature(self), EmbedSignature(self),
EarlyReplaceBuiltinCalls(self), ## Necessary? EarlyReplaceBuiltinCalls(self), ## Necessary?
TransformBuiltinMethods(self), ## Necessary?
CreateControlFlowGraph(self),
RemoveUnreachableCode(self),
MarkAssignments(self), MarkAssignments(self),
MarkOverflowingArithmetic(self), MarkOverflowingArithmetic(self),
TransformBuiltinMethods(self), ## Necessary?
IntroduceBufferAuxiliaryVars(self), IntroduceBufferAuxiliaryVars(self),
_check_c_declarations, _check_c_declarations,
AnalyseExpressionsTransform(self), AnalyseExpressionsTransform(self),
......
...@@ -136,6 +136,9 @@ class Node(object): ...@@ -136,6 +136,9 @@ class Node(object):
# can either contain a single node or a list of nodes. See Visitor.py. # can either contain a single node or a list of nodes. See Visitor.py.
child_attrs = None child_attrs = None
cf_state = None
def __init__(self, pos, **kw): def __init__(self, pos, **kw):
self.pos = pos self.pos = pos
self.__dict__.update(kw) self.__dict__.update(kw)
...@@ -1177,6 +1180,8 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1177,6 +1180,8 @@ class FuncDefNode(StatNode, BlockNode):
# needs_closure boolean Whether or not this function has inner functions/classes/yield # needs_closure boolean Whether or not this function has inner functions/classes/yield
# needs_outer_scope boolean Whether or not this function requires outer scope # needs_outer_scope boolean Whether or not this function requires outer scope
# directive_locals { string : NameNode } locals defined by cython.locals(...) # directive_locals { string : NameNode } locals defined by cython.locals(...)
# star_arg PyArgDeclNode or None * argument
# starstar_arg PyArgDeclNode or None ** argument
py_func = None py_func = None
assmt = None assmt = None
...@@ -1185,6 +1190,8 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1185,6 +1190,8 @@ class FuncDefNode(StatNode, BlockNode):
is_generator = False is_generator = False
is_generator_body = False is_generator_body = False
modifiers = [] modifiers = []
star_arg = None
starstar_arg = None
def analyse_default_values(self, env): def analyse_default_values(self, env):
genv = env.global_scope() genv = env.global_scope()
...@@ -1409,10 +1416,6 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1409,10 +1416,6 @@ class FuncDefNode(StatNode, BlockNode):
if entry.type.is_pyobject: if entry.type.is_pyobject:
if (acquire_gil or entry.assignments) and not entry.in_closure: if (acquire_gil or entry.assignments) and not entry.in_closure:
code.put_var_incref(entry) code.put_var_incref(entry)
# ----- Initialise local variables
for entry in lenv.var_entries:
if entry.type.is_pyobject and entry.init_to_none and entry.used:
code.put_init_var_to_py_none(entry)
# ----- Initialise local buffer auxiliary variables # ----- Initialise local buffer auxiliary variables
for entry in lenv.var_entries + lenv.arg_entries: for entry in lenv.var_entries + lenv.arg_entries:
if entry.type.is_buffer and entry.buffer_aux.buffer_info_var.used: if entry.type.is_buffer and entry.buffer_aux.buffer_info_var.used:
...@@ -1940,8 +1943,6 @@ class DefNode(FuncDefNode): ...@@ -1940,8 +1943,6 @@ class DefNode(FuncDefNode):
# lambda_name string the internal name of a lambda 'function' # lambda_name string the internal name of a lambda 'function'
# decorators [DecoratorNode] list of decorators # decorators [DecoratorNode] list of decorators
# args [CArgDeclNode] formal arguments # args [CArgDeclNode] formal arguments
# star_arg PyArgDeclNode or None * argument
# starstar_arg PyArgDeclNode or None ** argument
# doc EncodedString or None # doc EncodedString or None
# body StatListNode # body StatListNode
# return_type_annotation # return_type_annotation
...@@ -1966,8 +1967,6 @@ class DefNode(FuncDefNode): ...@@ -1966,8 +1967,6 @@ class DefNode(FuncDefNode):
entry = None entry = None
acquire_gil = 0 acquire_gil = 0
self_in_stararg = 0 self_in_stararg = 0
star_arg = None
starstar_arg = None
doc = None doc = None
def __init__(self, pos, **kwds): def __init__(self, pos, **kwds):
...@@ -2261,7 +2260,6 @@ class DefNode(FuncDefNode): ...@@ -2261,7 +2260,6 @@ class DefNode(FuncDefNode):
arg.entry = env.declare_var(arg.name, arg.type, arg.pos) arg.entry = env.declare_var(arg.name, arg.type, arg.pos)
if arg.type.is_pyobject: if arg.type.is_pyobject:
arg.entry.init = "0" arg.entry.init = "0"
arg.entry.init_to_none = 0
else: else:
arg.entry = self.declare_argument(env, arg) arg.entry = self.declare_argument(env, arg)
arg.entry.used = 1 arg.entry.used = 1
...@@ -2282,7 +2280,6 @@ class DefNode(FuncDefNode): ...@@ -2282,7 +2280,6 @@ class DefNode(FuncDefNode):
entry = env.declare_var(arg.name, type, arg.pos) entry = env.declare_var(arg.name, type, arg.pos)
entry.used = 1 entry.used = 1
entry.init = "0" entry.init = "0"
entry.init_to_none = 0
entry.xdecref_cleanup = 1 entry.xdecref_cleanup = 1
arg.entry = entry arg.entry = entry
env.control_flow.set_state((), (arg.name, 'initialized'), True) env.control_flow.set_state((), (arg.name, 'initialized'), True)
......
...@@ -3295,10 +3295,6 @@ class FinalOptimizePhase(Visitor.CythonTransform): ...@@ -3295,10 +3295,6 @@ class FinalOptimizePhase(Visitor.CythonTransform):
if node.first: if node.first:
lhs = node.lhs lhs = node.lhs
lhs.lhs_of_first_assignment = True lhs.lhs_of_first_assignment = True
if isinstance(lhs, ExprNodes.NameNode) and lhs.entry.type.is_pyobject:
# Have variable initialized to 0 rather than None
lhs.entry.init_to_none = False
lhs.entry.init = 0
return node return node
def visit_SimpleCallNode(self, node): def visit_SimpleCallNode(self, node):
......
...@@ -95,10 +95,19 @@ directive_defaults = { ...@@ -95,10 +95,19 @@ directive_defaults = {
'warn': None, 'warn': None,
'warn.undeclared': False, 'warn.undeclared': False,
'warn.unreachable': True, 'warn.unreachable': True,
'warn.maybe_uninitialized': False,
'warn.unreachable': True,
'warn.unused': False,
'warn.unused_arg': False,
'warn.unused_result': False,
# remove unreachable code # remove unreachable code
'remove_unreachable': True, 'remove_unreachable': True,
# control flow debug directives
'control_flow.dot_output': "", # Graphviz output filename
'control_flow.dot_annotate_defs': False, # Annotate definitions
# test support # test support
'test_assert_path_exists' : [], 'test_assert_path_exists' : [],
'test_fail_if_path_exists' : [], 'test_fail_if_path_exists' : [],
...@@ -107,6 +116,13 @@ directive_defaults = { ...@@ -107,6 +116,13 @@ directive_defaults = {
'binding': False, 'binding': False,
} }
# Extra warning directives
extra_warnings = {
'warn.maybe_uninitialized': True,
'warn.unreachable': True,
'warn.unused': True,
}
# Override types possibilities above, if needed # Override types possibilities above, if needed
directive_types = { directive_types = {
'final' : bool, # final cdef classes and methods 'final' : bool, # final cdef classes and methods
......
...@@ -1540,8 +1540,6 @@ if VALUE is not None: ...@@ -1540,8 +1540,6 @@ if VALUE is not None:
type_name = entry.type.module_name + '.' + type_name type_name = entry.type.module_name + '.' + type_name
if entry.init is not None: if entry.init is not None:
default_value = ' = ' + entry.init default_value = ' = ' + entry.init
elif entry.init_to_none:
default_value = ' = ' + repr(None)
docstring = attr_name + ': ' + type_name + default_value docstring = attr_name + ': ' + type_name + default_value
property.doc = EncodedString(docstring) property.doc = EncodedString(docstring)
# --------------------------------------- # ---------------------------------------
...@@ -2203,9 +2201,9 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2203,9 +2201,9 @@ class TransformBuiltinMethods(EnvTransform):
return node # nothing to do return node # nothing to do
items = [ ExprNodes.DictItemNode(pos, items = [ ExprNodes.DictItemNode(pos,
key=ExprNodes.StringNode(pos, value=var), key=ExprNodes.StringNode(pos, value=var),
value=ExprNodes.NameNode(pos, name=var)) value=ExprNodes.NameNode(pos, name=var, allow_null=True))
for var in lenv.entries ] for var in lenv.entries ]
return ExprNodes.DictNode(pos, key_value_pairs=items) return ExprNodes.DictNode(pos, key_value_pairs=items, exclude_null_values=True)
else: # dir() else: # dir()
if len(node.args) > 1: if len(node.args) > 1:
error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d" error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
......
...@@ -96,7 +96,6 @@ class Entry(object): ...@@ -96,7 +96,6 @@ class Entry(object):
# holding its home namespace # holding its home namespace
# pymethdef_cname string PyMethodDef structure # pymethdef_cname string PyMethodDef structure
# signature Signature Arg & return types for Python func # signature Signature Arg & return types for Python func
# init_to_none boolean True if initial value should be None
# as_variable Entry Alternative interpretation of extension # as_variable Entry Alternative interpretation of extension
# type name or builtin C function as a variable # type name or builtin C function as a variable
# xdecref_cleanup boolean Use Py_XDECREF for error cleanup # xdecref_cleanup boolean Use Py_XDECREF for error cleanup
...@@ -157,7 +156,6 @@ class Entry(object): ...@@ -157,7 +156,6 @@ class Entry(object):
func_cname = None func_cname = None
func_modifiers = [] func_modifiers = []
doc = None doc = None
init_to_none = 0
as_variable = None as_variable = None
xdecref_cleanup = 0 xdecref_cleanup = 0
in_cinclude = 0 in_cinclude = 0
...@@ -186,6 +184,8 @@ class Entry(object): ...@@ -186,6 +184,8 @@ class Entry(object):
self.init = init self.init = init
self.overloaded_alternatives = [] self.overloaded_alternatives = []
self.assignments = [] self.assignments = []
self.cf_assignments = []
self.cf_references = []
def __repr__(self): def __repr__(self):
return "Entry(name=%s, type=%s)" % (self.name, self.type) return "Entry(name=%s, type=%s)" % (self.name, self.type)
...@@ -1388,7 +1388,6 @@ class LocalScope(Scope): ...@@ -1388,7 +1388,6 @@ class LocalScope(Scope):
api=api, in_pxd=in_pxd, is_cdef=is_cdef) api=api, in_pxd=in_pxd, is_cdef=is_cdef)
if type.is_pyobject and not Options.init_local_none: if type.is_pyobject and not Options.init_local_none:
entry.init = "0" entry.init = "0"
entry.init_to_none = (type.is_pyobject or type.is_unspecified) and Options.init_local_none
entry.is_local = 1 entry.is_local = 1
entry.in_with_gil_block = self._in_with_gil_block entry.in_with_gil_block = self._in_with_gil_block
......
...@@ -101,6 +101,9 @@ def compile_cython_modules(profile=False, compile_more=False, cython_with_refnan ...@@ -101,6 +101,9 @@ def compile_cython_modules(profile=False, compile_more=False, cython_with_refnan
"Cython.Compiler.Visitor", "Cython.Compiler.Visitor",
"Cython.Compiler.Code", "Cython.Compiler.Code",
"Cython.Runtime.refnanny",] "Cython.Runtime.refnanny",]
if sys.version_info[:2] > (2,4):
compiled_modules += [
"Cython.Compiler.FlowControl",]
if compile_more: if compile_more:
compiled_modules.extend([ compiled_modules.extend([
"Cython.Compiler.ParseTreeTransforms", "Cython.Compiler.ParseTreeTransforms",
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
cdef void spam(): cdef void spam():
cdef long long L cdef long long L
cdef unsigned long long U cdef unsigned long long U
cdef object x cdef object x = object()
L = x L = x
x = L x = L
U = x U = x
......
# mode: compile # mode: compile
cdef int f() except -1: cdef int f() except -1:
cdef object x, y, z, w cdef object x, y = 0, z = 0, w = 0
cdef int i cdef int i
x = abs(y) x = abs(y)
delattr(x, 'spam') delattr(x, 'spam')
......
...@@ -14,7 +14,7 @@ cdef class SuperSpam(Spam): ...@@ -14,7 +14,7 @@ cdef class SuperSpam(Spam):
cdef void tomato(): cdef void tomato():
cdef Spam spam cdef Spam spam
cdef SuperSpam superspam cdef SuperSpam superspam = SuperSpam()
spam = superspam spam = superspam
spam.add_tons(42) spam.add_tons(42)
superspam.add_tons(1764) superspam.add_tons(1764)
......
...@@ -10,8 +10,8 @@ cdef class Swallow: ...@@ -10,8 +10,8 @@ cdef class Swallow:
def f(Grail g): def f(Grail g):
cdef int i = 0 cdef int i = 0
cdef Swallow s cdef Swallow s = Swallow()
cdef object x cdef object x = Grail()
g = x g = x
x = g x = g
g = i g = i
......
# ticket: 444 # ticket: 444
# mode: compile # mode: error
def test(): def test():
cdef object[int] not_assigned_to cdef object[int] not_assigned_to
not_assigned_to[2] = 3 not_assigned_to[2] = 3
_ERRORS = """
6:20: local variable 'not_assigned_to' referenced before assignment
"""
# mode: error # mode: error
cdef struct S: cdef struct S:
int m int m
def f(a): def f(a):
cdef int i, x[2] cdef int i, x[2]
cdef S s cdef S s
global j global j
del f() # error del f() # error
del i # error: deletion of non-Python object del i # error: deletion of non-Python object
del j # error: deletion of non-Python object del j # error: deletion of non-Python object
del x[i] # error: deletion of non-Python object del x[i] # error: deletion of non-Python object
del s.m # error: deletion of non-Python object del s.m # error: deletion of non-Python object
def outer(a):
def inner():
print a
del a
return inner()
_ERRORS = u""" _ERRORS = u"""
10:6: Cannot assign to or delete this 10:9: Cannot assign to or delete this
11:45: Deletion of non-Python, non-C++ object 11:48: Deletion of non-Python, non-C++ object
13:6: Deletion of non-Python, non-C++ object 13:9: Deletion of non-Python, non-C++ object
14:6: Deletion of non-Python, non-C++ object 14:9: Deletion of non-Python, non-C++ object
19:9: can not delete variable 'a' referenced in nested scope
""" """
...@@ -10,7 +10,7 @@ cdef class E: ...@@ -10,7 +10,7 @@ cdef class E:
cdef readonly object __weakref__ cdef readonly object __weakref__
cdef void f(): cdef void f():
cdef C c cdef C c = C()
cdef object x cdef object x
x = c.__weakref__ x = c.__weakref__
c.__weakref__ = x c.__weakref__ = x
......
...@@ -18,7 +18,7 @@ cdef void r() nogil: ...@@ -18,7 +18,7 @@ cdef void r() nogil:
q() q()
cdef object m(): cdef object m():
cdef object x, y, obj cdef object x, y = 0, obj
cdef int i, j, k cdef int i, j, k
global fred global fred
q() q()
......
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def simple():
print a
a = 0
def simple2(arg):
if arg > 0:
a = 1
return a
def simple_pos(arg):
if arg > 0:
a = 1
else:
a = 0
return a
def ifelif(c1, c2):
if c1 == 1:
if c2:
a = 1
else:
a = 2
elif c1 == 2:
a = 3
return a
def nowimpossible(a):
if a:
b = 1
if a:
print b
def fromclosure():
def bar():
print a
a = 1
return bar
# Should work ok in both py2 and py3
def list_comp(a):
return [i for i in a]
def set_comp(a):
return set(i for i in a)
def dict_comp(a):
return {i: j for i, j in a}
# args and kwargs
def generic_args_call(*args, **kwargs):
return args, kwargs
def cascaded(x):
print a, b
a = b = x
def from_import():
print bar
from foo import bar
_ERRORS = """
6:11: local variable 'a' referenced before assignment
12:12: local variable 'a' might be referenced before assignment
29:12: local variable 'a' might be referenced before assignment
35:15: local variable 'b' might be referenced before assignment
58:11: local variable 'a' referenced before assignment
58:14: local variable 'b' referenced before assignment
62:13: local variable 'bar' referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
# class scope
def foo(c):
class Foo(object):
if c > 0:
b = 1
print a, b
a = 1
return Foo
_ERRORS = """
10:15: local variable 'a' referenced before assignment
10:18: local variable 'b' might be referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def foo(x):
a = 1
del a, b
b = 2
return a, b
_ERRORS = """
7:9: Deletion of non-Python, non-C++ object
7:12: local variable 'b' referenced before assignment
7:12: Deletion of non-Python, non-C++ object
9:12: local variable 'a' referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def exc_target():
try:
{}['foo']
except KeyError, e:
pass
except IndexError, i:
pass
return e, i
def exc_body():
try:
a = 1
except Exception:
pass
return a
def exc_else_pos():
try:
pass
except Exception, e:
pass
else:
e = 1
return e
def exc_body_pos(d):
try:
a = d['foo']
except KeyError:
a = None
return a
def exc_pos():
try:
a = 1
except Exception:
a = 1
return a
def exc_finally():
try:
a = 1
finally:
pass
return a
def exc_finally2():
try:
pass
finally:
a = 1
return a
def exc_assmt_except(a):
try:
x = a
except:
return x
def exc_assmt_finaly(a):
try:
x = a
except:
return x
def raise_stat(a):
try:
if a < 0:
raise IndexError
except IndexError:
oops = 1
print oops
def try_loop(args):
try:
x = 0
for i in args:
if i is 0:
continue
elif i is None:
break
elif i is False:
return
i()
except ValueError:
x = 1
finally:
return x
def try_finally(a):
try:
for i in a:
if i > 0:
x = 1
finally:
return x
def try_finally_nested(m):
try:
try:
try:
f = m()
except:
pass
finally:
pass
except:
print f
_ERRORS = """
12:12: local variable 'e' might be referenced before assignment
12:15: local variable 'i' might be referenced before assignment
19:12: local variable 'a' might be referenced before assignment
63:16: local variable 'x' might be referenced before assignment
69:16: local variable 'x' might be referenced before assignment
77:14: local variable 'oops' might be referenced before assignment
93:16: local variable 'x' might be referenced before assignment
101:16: local variable 'x' might be referenced before assignment
113:15: local variable 'f' might be referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def simple_for(n):
for i in n:
a = 1
return a
def simple_for_break(n):
for i in n:
a = 1
break
return a
def simple_for_pos(n):
for i in n:
a = 1
else:
a = 0
return a
def simple_target(n):
for i in n:
pass
return i
def simple_target_f(n):
for i in n:
i *= i
return i
def simple_for_from(n):
for i from 0 <= i <= n:
x = i
else:
return x
def for_continue(l):
for i in l:
if i > 0:
continue
x = i
print x
def for_break(l):
for i in l:
if i > 0:
break
x = i
print x
def for_finally_continue(f):
for i in f:
try:
x = i()
finally:
print x
continue
def for_finally_break(f):
for i in f:
try:
x = i()
finally:
print x
break
def for_finally_outer(p, f):
x = 1
try:
for i in f:
print x
x = i()
if x > 0:
continue
if x < 0:
break
finally:
del x
_ERRORS = """
8:12: local variable 'a' might be referenced before assignment
14:12: local variable 'a' might be referenced before assignment
26:12: local variable 'i' might be referenced before assignment
31:12: local variable 'i' might be referenced before assignment
37:16: local variable 'x' might be referenced before assignment
44:11: local variable 'x' might be referenced before assignment
51:11: local variable 'x' might be referenced before assignment
58:19: local variable 'x' might be referenced before assignment
66:19: local variable 'x' might be referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def unbound_inside_generator(*args):
for i in args:
yield x
x = i + i
_ERRORS = """
7:15: local variable 'x' might be referenced before assignment
"""
# cython: language_level=2, warn.maybe_uninitialized=True
# mode: error
# tag: werror
def list_comp(a):
r = [i for i in a]
return i
# dict comp is py3 feuture and don't leak here
def dict_comp(a):
r = {i: j for i, j in a}
return i, j
def dict_comp2(a):
r = {i: j for i, j in a}
print i, j
i, j = 0, 0
_ERRORS = """
7:12: local variable 'i' might be referenced before assignment
12:12: undeclared name not builtin: i
12:15: undeclared name not builtin: j
16:11: local variable 'i' referenced before assignment
16:14: local variable 'j' referenced before assignment
"""
# cython: language_level=3, warn.maybe_uninitialized=True
# mode: error
# tag: werror
def ref(obj):
pass
def list_comp(a):
r = [i for i in a]
ref(i)
i = 0
return r
def dict_comp(a):
r = {i: j for i, j in a}
ref(i)
i = 0
return r
_ERRORS = """
10:9: local variable 'i' referenced before assignment
16:9: local variable 'i' referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def simple_while(n):
while n > 0:
n -= 1
a = 0
return a
def simple_while_break(n):
while n > 0:
n -= 1
break
else:
a = 1
return a
def simple_while_pos(n):
while n > 0:
n -= 1
a = 0
else:
a = 1
return a
def while_finally_continue(p, f):
while p():
try:
x = f()
finally:
print x
continue
def while_finally_break(p, f):
while p():
try:
x = f()
finally:
print x
break
def while_finally_outer(p, f):
x = 1
try:
while p():
print x
x = f()
if x > 0:
continue
if x < 0:
break
finally:
del x
_ERRORS = """
9:12: local variable 'a' might be referenced before assignment
17:12: local variable 'a' might be referenced before assignment
32:19: local variable 'x' might be referenced before assignment
40:19: local variable 'x' might be referenced before assignment
"""
# cython: warn.maybe_uninitialized=True
# mode: error
# tag: werror
def with_no_target(m):
with m:
print a
a = 1
def unbound_manager(m1):
with m2:
pass
m2 = m1
def with_target(m):
with m as f:
print(f)
def with_mgr(m):
try:
with m() as f:
pass
except:
print f
_ERRORS = """
7:15: local variable 'a' referenced before assignment
11:11: local variable 'm2' referenced before assignment
24:15: local variable 'f' might be referenced before assignment
"""
# mode: error
# tag: werror, unreachable, control-flow
def try_finally():
try:
return
finally:
return
print 'oops'
def try_return():
try:
return
except:
return
print 'oops'
def for_return(a):
for i in a:
return
else:
return
print 'oops'
def while_return(a):
while a:
return
else:
return
print 'oops'
def forfrom_return(a):
for i from 0 <= i <= a:
return
else:
return
print 'oops'
_ERRORS = """
9:4: Unreachable code
16:4: Unreachable code
23:4: Unreachable code
30:4: Unreachable code
37:4: Unreachable code
"""
# cython: warn.unused=True, warn.unused_arg=True, warn.unused_result=True
# mode: error
# tag: werror
def unused_variable():
a = 1
def unused_cascade(arg):
a, b = arg.split()
return a
def unused_arg(arg):
pass
def unused_result():
r = 1 + 1
r = 2
return r
def unused_nested():
def unused_one():
pass
def unused_class():
class Unused:
pass
# this should not generate warning
def used(x, y):
x.y = 1
y[0] = 1
lambda x: x
_ERRORS = """
6:6: Unused entry 'a'
9:9: Unused entry 'b'
12:15: Unused argument 'arg'
16:6: Unused result in 'r'
21:4: Unused entry 'unused_one'
25:4: Unused entry 'Unused'
"""
...@@ -47,15 +47,6 @@ def nousage(): ...@@ -47,15 +47,6 @@ def nousage():
""" """
cdef object[int, ndim=2] buf cdef object[int, ndim=2] buf
def printbuf():
"""
Just compilation.
"""
cdef object[int, ndim=2] buf
print buf
return
buf[0,0] = 0
@testcase @testcase
def acquire_release(o1, o2): def acquire_release(o1, o2):
""" """
...@@ -681,20 +672,20 @@ def mixed_get(object[int] buf, int unsafe_idx, int safe_idx): ...@@ -681,20 +672,20 @@ def mixed_get(object[int] buf, int unsafe_idx, int safe_idx):
# #
# Coercions # Coercions
# #
@testcase ## @testcase
def coercions(object[unsigned char] uc): ## def coercions(object[unsigned char] uc):
""" ## """
TODO ## TODO
""" ## """
print type(uc[0]) ## print type(uc[0])
uc[0] = -1 ## uc[0] = -1
print uc[0] ## print uc[0]
uc[0] = <int>3.14 ## uc[0] = <int>3.14
print uc[0] ## print uc[0]
cdef char* ch = b"asfd" ## cdef char* ch = b"asfd"
cdef object[object] objbuf ## cdef object[object] objbuf
objbuf[3] = ch ## objbuf[3] = ch
# #
......
...@@ -86,13 +86,13 @@ def del_local(a): ...@@ -86,13 +86,13 @@ def del_local(a):
>>> del_local(object()) >>> del_local(object())
""" """
del a del a
assert a is None # Until we have unbound locals... assert 'a' not in locals()
def del_seq(a, b, c): def del_seq(a, b, c):
""" """
>>> del_seq(1, 2, 3) >>> del_seq(1, 2, 3)
""" """
del a, (b, c) del a, (b, c)
assert a is None # Until we have unbound locals... assert 'a' not in locals()
assert b is None # Until we have unbound locals... assert 'b' not in locals()
assert c is None # Until we have unbound locals... assert 'c' not in locals()
...@@ -8,7 +8,7 @@ __doc__ = u""" ...@@ -8,7 +8,7 @@ __doc__ = u"""
[('args', (2, 3)), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)] [('args', (2, 3)), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)]
>>> sorted(get_locals_items_listcomp(1,2,3, k=5)) >>> sorted(get_locals_items_listcomp(1,2,3, k=5))
[('args', (2, 3)), ('item', None), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)] [('args', (2, 3)), ('kwds', {'k': 5}), ('x', 1), ('y', 'hi'), ('z', 5)]
""" """
def get_locals(x, *args, **kwds): def get_locals(x, *args, **kwds):
......
# mode: run
# tag: control-flow, uninitialized
def conditional(cond):
"""
>>> conditional(True)
[]
>>> conditional(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
if cond:
a = []
return a
def inside_loop(iter):
"""
>>> inside_loop([1,2,3])
3
>>> inside_loop([])
Traceback (most recent call last):
...
UnboundLocalError: local variable 'i' referenced before assignment
"""
for i in iter:
pass
return i
def try_except(cond):
"""
>>> try_except(True)
[]
>>> try_except(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
try:
if cond:
a = []
raise ValueError
except ValueError:
return a
def try_finally(cond):
"""
>>> try_finally(True)
[]
>>> try_finally(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
try:
if cond:
a = []
raise ValueError
finally:
return a
def deleted(cond):
"""
>>> deleted(False)
{}
>>> deleted(True)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
a = {}
if cond:
del a
return a
def test_nested(cond):
"""
>>> test_nested(True)
>>> test_nested(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
if cond:
def a():
pass
return a()
def test_outer(cond):
"""
>>> test_outer(True)
{}
>>> test_outer(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
"""
if cond:
a = {}
def inner():
return a
return a
def test_inner(cond):
"""
>>> test_inner(True)
{}
>>> test_inner(False)
Traceback (most recent call last):
...
NameError: free variable 'a' referenced before assignment in enclosing scope
"""
if cond:
a = {}
def inner():
return a
return inner()
def test_class(cond):
"""
>>> test_class(True)
1
>>> test_class(False)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'A' referenced before assignment
"""
if cond:
class A:
x = 1
return A.x
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