bpo-17611. Move unwinding of stack for "pseudo exceptions" from interpreter to compiler. (GH-5006)

Co-authored-by: Mark Shannon <mark@hotpy.org>
Co-authored-by: Antoine Pitrou <antoine@python.org>
This commit is contained in:
Serhiy Storchaka 2018-02-22 23:33:30 +02:00 committed by GitHub
parent 4af8fd5614
commit 520b7ae27e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 4506 additions and 4392 deletions

View file

@ -45,6 +45,27 @@ frame_getlineno(PyFrameObject *f, void *closure)
return PyLong_FromLong(PyFrame_GetLineNumber(f));
}
/* Given the index of the effective opcode,
scan back to construct the oparg with EXTENDED_ARG */
static unsigned int
get_arg(const _Py_CODEUNIT *codestr, Py_ssize_t i)
{
_Py_CODEUNIT word;
unsigned int oparg = _Py_OPARG(codestr[i]);
if (i >= 1 && _Py_OPCODE(word = codestr[i-1]) == EXTENDED_ARG) {
oparg |= _Py_OPARG(word) << 8;
if (i >= 2 && _Py_OPCODE(word = codestr[i-2]) == EXTENDED_ARG) {
oparg |= _Py_OPARG(word) << 16;
if (i >= 3 && _Py_OPCODE(word = codestr[i-3]) == EXTENDED_ARG) {
oparg |= _Py_OPARG(word) << 24;
}
}
}
return oparg;
}
/* Setter for f_lineno - you can set f_lineno from within a trace function in
* order to jump to a given line of code, subject to some restrictions. Most
* lines are OK to jump to because they don't make any assumptions about the
@ -56,8 +77,9 @@ frame_getlineno(PyFrameObject *f, void *closure)
* they expect an exception to be on the top of the stack.
* o Lines that live in a 'finally' block can't be jumped from or to, since
* the END_FINALLY expects to clean up the stack after the 'try' block.
* o 'try'/'for'/'while' blocks can't be jumped into because the blockstack
* needs to be set up before their code runs, and for 'for' loops the
* o 'try', 'with' and 'async with' blocks can't be jumped into because
* the blockstack needs to be set up before their code runs.
* o 'for' and 'async for' loops can't be jumped into because the
* iterator needs to be on the stack.
*/
static int
@ -75,17 +97,10 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
int offset = 0; /* (ditto) */
int line = 0; /* (ditto) */
int addr = 0; /* (ditto) */
int min_addr = 0; /* Scanning the SETUPs and POPs */
int max_addr = 0; /* (ditto) */
int delta_iblock = 0; /* (ditto) */
int min_delta_iblock = 0; /* (ditto) */
int min_iblock = 0; /* (ditto) */
int f_lasti_setup_addr = 0; /* Policing no-jump-into-finally */
int new_lasti_setup_addr = 0; /* (ditto) */
int delta_iblock = 0; /* Scanning the SETUPs and POPs */
int for_loop_delta = 0; /* (ditto) */
int blockstack[CO_MAXBLOCKS]; /* Walking the 'finally' blocks */
int in_finally[CO_MAXBLOCKS]; /* (ditto) */
int blockstack_top = 0; /* (ditto) */
unsigned char setup_op = 0; /* (ditto) */
/* f_lineno must be an integer. */
if (!PyLong_CheckExact(p_new_lineno)) {
@ -159,8 +174,6 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
/* We're now ready to look at the bytecode. */
PyBytes_AsStringAndSize(f->f_code->co_code, (char **)&code, &code_len);
min_addr = Py_MIN(new_lasti, f->f_lasti);
max_addr = Py_MAX(new_lasti, f->f_lasti);
/* You can't jump onto a line with an 'except' statement on it -
* they expect to have an exception on the top of the stack, which
@ -179,141 +192,73 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
}
/* You can't jump into or out of a 'finally' block because the 'try'
* block leaves something on the stack for the END_FINALLY to clean
* up. So we walk the bytecode, maintaining a simulated blockstack.
* When we reach the old or new address and it's in a 'finally' block
* we note the address of the corresponding SETUP_FINALLY. The jump
* is only legal if neither address is in a 'finally' block or
* they're both in the same one. 'blockstack' is a stack of the
* bytecode addresses of the SETUP_X opcodes, and 'in_finally' tracks
* whether we're in a 'finally' block at each blockstack level. */
f_lasti_setup_addr = -1;
new_lasti_setup_addr = -1;
* block leaves something on the stack for the END_FINALLY to clean up.
* So we walk the bytecode, maintaining a simulated blockstack.
* 'blockstack' is a stack of the bytecode addresses of the starts of
* the 'finally' blocks. */
memset(blockstack, '\0', sizeof(blockstack));
memset(in_finally, '\0', sizeof(in_finally));
blockstack_top = 0;
for (addr = 0; addr < code_len; addr += sizeof(_Py_CODEUNIT)) {
unsigned char op = code[addr];
switch (op) {
case SETUP_LOOP:
case SETUP_EXCEPT:
case SETUP_FINALLY:
case SETUP_WITH:
case SETUP_ASYNC_WITH:
blockstack[blockstack_top++] = addr;
in_finally[blockstack_top-1] = 0;
break;
case POP_BLOCK:
assert(blockstack_top > 0);
setup_op = code[blockstack[blockstack_top-1]];
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH
|| setup_op == SETUP_ASYNC_WITH) {
in_finally[blockstack_top-1] = 1;
case FOR_ITER: {
unsigned int oparg = get_arg((const _Py_CODEUNIT *)code,
addr / sizeof(_Py_CODEUNIT));
int target_addr = addr + oparg + sizeof(_Py_CODEUNIT);
assert(target_addr < code_len);
/* Police block-jumping (you can't jump into the middle of a block)
* and ensure that the blockstack finishes up in a sensible state (by
* popping any blocks we're jumping out of). We look at all the
* blockstack operations between the current position and the new
* one, and keep track of how many blocks we drop out of on the way.
* By also keeping track of the lowest blockstack position we see, we
* can tell whether the jump goes into any blocks without coming out
* again - in that case we raise an exception below. */
int first_in = addr < f->f_lasti && f->f_lasti < target_addr;
int second_in = addr < new_lasti && new_lasti < target_addr;
if (!first_in && second_in) {
PyErr_SetString(PyExc_ValueError,
"can't jump into the middle of a block");
return -1;
}
else {
blockstack_top--;
}
break;
case END_FINALLY:
/* Ignore END_FINALLYs for SETUP_EXCEPTs - they exist
* in the bytecode but don't correspond to an actual
* 'finally' block. (If blockstack_top is 0, we must
* be seeing such an END_FINALLY.) */
if (blockstack_top > 0) {
setup_op = code[blockstack[blockstack_top-1]];
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH
|| setup_op == SETUP_ASYNC_WITH) {
blockstack_top--;
if (first_in && !second_in) {
if (op == FOR_ITER && !delta_iblock) {
for_loop_delta++;
}
if (op != FOR_ITER) {
delta_iblock++;
}
}
if (op != FOR_ITER) {
blockstack[blockstack_top++] = target_addr;
}
break;
}
/* For the addresses we're interested in, see whether they're
* within a 'finally' block and if so, remember the address
* of the SETUP_FINALLY. */
if (addr == new_lasti || addr == f->f_lasti) {
int i = 0;
int setup_addr = -1;
for (i = blockstack_top-1; i >= 0; i--) {
if (in_finally[i]) {
setup_addr = blockstack[i];
break;
}
}
if (setup_addr != -1) {
if (addr == new_lasti) {
new_lasti_setup_addr = setup_addr;
}
if (addr == f->f_lasti) {
f_lasti_setup_addr = setup_addr;
}
case END_FINALLY: {
assert(blockstack_top > 0);
int target_addr = blockstack[--blockstack_top];
assert(target_addr <= addr);
int first_in = target_addr <= f->f_lasti && f->f_lasti <= addr;
int second_in = target_addr <= new_lasti && new_lasti <= addr;
if (first_in != second_in) {
PyErr_SetString(PyExc_ValueError,
"can't jump into or out of a 'finally' block");
return -1;
}
break;
}
}
}
/* Verify that the blockstack tracking code didn't get lost. */
assert(blockstack_top == 0);
/* After all that, are we jumping into / out of a 'finally' block? */
if (new_lasti_setup_addr != f_lasti_setup_addr) {
PyErr_SetString(PyExc_ValueError,
"can't jump into or out of a 'finally' block");
return -1;
}
/* Police block-jumping (you can't jump into the middle of a block)
* and ensure that the blockstack finishes up in a sensible state (by
* popping any blocks we're jumping out of). We look at all the
* blockstack operations between the current position and the new
* one, and keep track of how many blocks we drop out of on the way.
* By also keeping track of the lowest blockstack position we see, we
* can tell whether the jump goes into any blocks without coming out
* again - in that case we raise an exception below. */
delta_iblock = 0;
for (addr = min_addr; addr < max_addr; addr += sizeof(_Py_CODEUNIT)) {
unsigned char op = code[addr];
switch (op) {
case SETUP_LOOP:
case SETUP_EXCEPT:
case SETUP_FINALLY:
case SETUP_WITH:
case SETUP_ASYNC_WITH:
delta_iblock++;
break;
case POP_BLOCK:
delta_iblock--;
break;
}
min_delta_iblock = Py_MIN(min_delta_iblock, delta_iblock);
}
/* Derive the absolute iblock values from the deltas. */
min_iblock = f->f_iblock + min_delta_iblock;
if (new_lasti > f->f_lasti) {
/* Forwards jump. */
new_iblock = f->f_iblock + delta_iblock;
}
else {
/* Backwards jump. */
new_iblock = f->f_iblock - delta_iblock;
}
/* Are we jumping into a block? */
if (new_iblock > min_iblock) {
PyErr_SetString(PyExc_ValueError,
"can't jump into the middle of a block");
return -1;
}
/* Pop any blocks that we're jumping out of. */
new_iblock = f->f_iblock - delta_iblock;
while (f->f_iblock > new_iblock) {
PyTryBlock *b = &f->f_blockstack[--f->f_iblock];
while ((f->f_stacktop - f->f_valuestack) > b->b_level) {
@ -321,6 +266,12 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
Py_DECREF(v);
}
}
/* Pop the iterators of any 'for' loop we're jumping out of. */
while (for_loop_delta > 0) {
PyObject *v = (*--f->f_stacktop);
Py_DECREF(v);
for_loop_delta--;
}
/* Finally set the new f_lineno and f_lasti and return OK. */
f->f_lineno = new_lineno;