py: Make all LOAD_FAST ops check for unbound local.
This is necessary to catch all cases where locals are referenced before assignment. We still keep the _0, _1, _2 versions of LOAD_FAST to help reduced the byte code size in RAM. Addresses issue #457.
This commit is contained in:
parent
c2803db010
commit
6ce4277551
1
py/bc0.h
1
py/bc0.h
@ -17,7 +17,6 @@
|
||||
#define MP_BC_LOAD_FAST_1 (0x21)
|
||||
#define MP_BC_LOAD_FAST_2 (0x22)
|
||||
#define MP_BC_LOAD_FAST_N (0x23) // uint
|
||||
#define MP_BC_LOAD_FAST_CHECKED (0x24) // uint
|
||||
#define MP_BC_LOAD_DEREF (0x25) // uint
|
||||
#define MP_BC_LOAD_NAME (0x26) // qstr
|
||||
#define MP_BC_LOAD_GLOBAL (0x27) // qstr
|
||||
|
16
py/emitbc.c
16
py/emitbc.c
@ -413,17 +413,11 @@ STATIC void emit_bc_load_null(emit_t *emit) {
|
||||
STATIC void emit_bc_load_fast(emit_t *emit, qstr qstr, uint id_flags, int local_num) {
|
||||
assert(local_num >= 0);
|
||||
emit_bc_pre(emit, 1);
|
||||
if (id_flags & ID_FLAG_IS_DELETED) {
|
||||
// This local may be deleted, so need to do a checked load.
|
||||
emit_write_byte_code_byte_uint(emit, MP_BC_LOAD_FAST_CHECKED, local_num);
|
||||
} else {
|
||||
// This local is never deleted, so can do a fast, uncheched load.
|
||||
switch (local_num) {
|
||||
case 0: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_0); break;
|
||||
case 1: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_1); break;
|
||||
case 2: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_2); break;
|
||||
default: emit_write_byte_code_byte_uint(emit, MP_BC_LOAD_FAST_N, local_num); break;
|
||||
}
|
||||
switch (local_num) {
|
||||
case 0: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_0); break;
|
||||
case 1: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_1); break;
|
||||
case 2: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_2); break;
|
||||
default: emit_write_byte_code_byte_uint(emit, MP_BC_LOAD_FAST_N, local_num); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,9 @@ STATIC void emit_pass1_store_id(emit_t *emit, qstr qstr) {
|
||||
|
||||
STATIC void emit_pass1_delete_id(emit_t *emit, qstr qstr) {
|
||||
id_info_t *id = get_id_for_modification(emit->scope, qstr);
|
||||
id->flags |= ID_FLAG_IS_DELETED;
|
||||
// this flag is unused
|
||||
//id->flags |= ID_FLAG_IS_DELETED;
|
||||
(void)id; // suppress compiler warning
|
||||
}
|
||||
|
||||
const emit_method_table_t emit_pass1_method_table = {
|
||||
|
@ -132,11 +132,6 @@ void mp_byte_code_print(const byte *ip, int len) {
|
||||
printf("LOAD_FAST_N " UINT_FMT, unum);
|
||||
break;
|
||||
|
||||
case MP_BC_LOAD_FAST_CHECKED:
|
||||
DECODE_UINT;
|
||||
printf("LOAD_FAST_CHECKED " UINT_FMT, unum);
|
||||
break;
|
||||
|
||||
case MP_BC_LOAD_DEREF:
|
||||
DECODE_UINT;
|
||||
printf("LOAD_DEREF " UINT_FMT, unum);
|
||||
|
24
py/vm.c
24
py/vm.c
@ -252,25 +252,21 @@ dispatch_loop:
|
||||
break;
|
||||
|
||||
case MP_BC_LOAD_FAST_0:
|
||||
PUSH(fastn[0]);
|
||||
break;
|
||||
obj1 = fastn[0];
|
||||
goto load_check;
|
||||
|
||||
case MP_BC_LOAD_FAST_1:
|
||||
PUSH(fastn[-1]);
|
||||
break;
|
||||
obj1 = fastn[-1];
|
||||
goto load_check;
|
||||
|
||||
case MP_BC_LOAD_FAST_2:
|
||||
PUSH(fastn[-2]);
|
||||
break;
|
||||
obj1 = fastn[-2];
|
||||
goto load_check;
|
||||
|
||||
case MP_BC_LOAD_FAST_N:
|
||||
DECODE_UINT;
|
||||
PUSH(fastn[-unum]);
|
||||
break;
|
||||
|
||||
case MP_BC_LOAD_FAST_CHECKED:
|
||||
DECODE_UINT;
|
||||
obj1 = fastn[-unum];
|
||||
load_check:
|
||||
if (obj1 == MP_OBJ_NULL) {
|
||||
local_name_error:
|
||||
nlr_raise(mp_obj_new_exception_msg(&mp_type_NameError, "local variable referenced before assignment"));
|
||||
@ -281,11 +277,7 @@ dispatch_loop:
|
||||
case MP_BC_LOAD_DEREF:
|
||||
DECODE_UINT;
|
||||
obj1 = mp_obj_cell_get(fastn[-unum]);
|
||||
if (obj1 == MP_OBJ_NULL) {
|
||||
goto local_name_error;
|
||||
}
|
||||
PUSH(obj1);
|
||||
break;
|
||||
goto load_check;
|
||||
|
||||
case MP_BC_LOAD_NAME:
|
||||
DECODE_QSTR;
|
||||
|
19
tests/basics/unboundlocal.py
Normal file
19
tests/basics/unboundlocal.py
Normal file
@ -0,0 +1,19 @@
|
||||
# locals referenced before assignment
|
||||
|
||||
def f1():
|
||||
print(x)
|
||||
x = 1
|
||||
|
||||
def f2():
|
||||
for i in range(0):
|
||||
print(i)
|
||||
print(i)
|
||||
|
||||
def check(f):
|
||||
try:
|
||||
f()
|
||||
except NameError:
|
||||
print("NameError")
|
||||
|
||||
check(f1)
|
||||
check(f2)
|
Loading…
Reference in New Issue
Block a user