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_1 (0x21)
|
||||||
#define MP_BC_LOAD_FAST_2 (0x22)
|
#define MP_BC_LOAD_FAST_2 (0x22)
|
||||||
#define MP_BC_LOAD_FAST_N (0x23) // uint
|
#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_DEREF (0x25) // uint
|
||||||
#define MP_BC_LOAD_NAME (0x26) // qstr
|
#define MP_BC_LOAD_NAME (0x26) // qstr
|
||||||
#define MP_BC_LOAD_GLOBAL (0x27) // 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) {
|
STATIC void emit_bc_load_fast(emit_t *emit, qstr qstr, uint id_flags, int local_num) {
|
||||||
assert(local_num >= 0);
|
assert(local_num >= 0);
|
||||||
emit_bc_pre(emit, 1);
|
emit_bc_pre(emit, 1);
|
||||||
if (id_flags & ID_FLAG_IS_DELETED) {
|
switch (local_num) {
|
||||||
// This local may be deleted, so need to do a checked load.
|
case 0: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_0); break;
|
||||||
emit_write_byte_code_byte_uint(emit, MP_BC_LOAD_FAST_CHECKED, local_num);
|
case 1: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_1); break;
|
||||||
} else {
|
case 2: emit_write_byte_code_byte(emit, MP_BC_LOAD_FAST_2); break;
|
||||||
// This local is never deleted, so can do a fast, uncheched load.
|
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) {
|
STATIC void emit_pass1_delete_id(emit_t *emit, qstr qstr) {
|
||||||
id_info_t *id = get_id_for_modification(emit->scope, 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 = {
|
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);
|
printf("LOAD_FAST_N " UINT_FMT, unum);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_CHECKED:
|
|
||||||
DECODE_UINT;
|
|
||||||
printf("LOAD_FAST_CHECKED " UINT_FMT, unum);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MP_BC_LOAD_DEREF:
|
case MP_BC_LOAD_DEREF:
|
||||||
DECODE_UINT;
|
DECODE_UINT;
|
||||||
printf("LOAD_DEREF " UINT_FMT, unum);
|
printf("LOAD_DEREF " UINT_FMT, unum);
|
||||||
|
24
py/vm.c
24
py/vm.c
@ -252,25 +252,21 @@ dispatch_loop:
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_0:
|
case MP_BC_LOAD_FAST_0:
|
||||||
PUSH(fastn[0]);
|
obj1 = fastn[0];
|
||||||
break;
|
goto load_check;
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_1:
|
case MP_BC_LOAD_FAST_1:
|
||||||
PUSH(fastn[-1]);
|
obj1 = fastn[-1];
|
||||||
break;
|
goto load_check;
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_2:
|
case MP_BC_LOAD_FAST_2:
|
||||||
PUSH(fastn[-2]);
|
obj1 = fastn[-2];
|
||||||
break;
|
goto load_check;
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_N:
|
case MP_BC_LOAD_FAST_N:
|
||||||
DECODE_UINT;
|
|
||||||
PUSH(fastn[-unum]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MP_BC_LOAD_FAST_CHECKED:
|
|
||||||
DECODE_UINT;
|
DECODE_UINT;
|
||||||
obj1 = fastn[-unum];
|
obj1 = fastn[-unum];
|
||||||
|
load_check:
|
||||||
if (obj1 == MP_OBJ_NULL) {
|
if (obj1 == MP_OBJ_NULL) {
|
||||||
local_name_error:
|
local_name_error:
|
||||||
nlr_raise(mp_obj_new_exception_msg(&mp_type_NameError, "local variable referenced before assignment"));
|
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:
|
case MP_BC_LOAD_DEREF:
|
||||||
DECODE_UINT;
|
DECODE_UINT;
|
||||||
obj1 = mp_obj_cell_get(fastn[-unum]);
|
obj1 = mp_obj_cell_get(fastn[-unum]);
|
||||||
if (obj1 == MP_OBJ_NULL) {
|
goto load_check;
|
||||||
goto local_name_error;
|
|
||||||
}
|
|
||||||
PUSH(obj1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MP_BC_LOAD_NAME:
|
case MP_BC_LOAD_NAME:
|
||||||
DECODE_QSTR;
|
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