summaryrefslogtreecommitdiffstats
path: root/Objects/frameobject.c
diff options
context:
space:
mode:
Diffstat (limited to 'Objects/frameobject.c')
-rw-r--r--Objects/frameobject.c260
1 files changed, 259 insertions, 1 deletions
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 3036ab6..b7b3021 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -8,6 +8,9 @@
#include "opcode.h"
#include "structmember.h"
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
#define OFF(x) offsetof(PyFrameObject, x)
static PyMemberDef frame_memberlist[] = {
@@ -44,6 +47,260 @@ frame_getlineno(PyFrameObject *f, void *closure)
return PyInt_FromLong(lineno);
}
+/* 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
+ * state of the stack (obvious because you could remove the line and the code
+ * would still work without any stack errors), but there are some constructs
+ * that limit jumping:
+ *
+ * o Lines with an 'except' statement on them can't be jumped to, because
+ * 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
+ * iterator needs to be on the stack.
+ */
+static int
+frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
+{
+ int new_lineno = 0; /* The new value of f_lineno */
+ int new_lasti = 0; /* The new value of f_lasti */
+ int new_iblock = 0; /* The new value of f_iblock */
+ char *code = NULL; /* The bytecode for the frame... */
+ int code_len = 0; /* ...and its length */
+ char *lnotab = NULL; /* Iterating over co_lnotab */
+ int lnotab_len = 0; /* (ditto) */
+ 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 blockstack[CO_MAXBLOCKS]; /* Walking the 'finally' blocks */
+ int in_finally[CO_MAXBLOCKS]; /* (ditto) */
+ int blockstack_top = 0; /* (ditto) */
+ int setup_op = 0; /* (ditto) */
+
+ /* f_lineno must be an integer. */
+ if (!PyInt_Check(p_new_lineno)) {
+ PyErr_SetString(PyExc_ValueError,
+ "lineno must be an integer");
+ return -1;
+ }
+
+ /* You can only do this from within a trace function, not via
+ * _getframe or similar hackery. */
+ if (!f->f_trace)
+ {
+ PyErr_Format(PyExc_ValueError,
+ "f_lineno can only be set by a trace function");
+ return -1;
+ }
+
+ /* Fail if the line comes before the start of the code block. */
+ new_lineno = (int) PyInt_AsLong(p_new_lineno);
+ if (new_lineno < f->f_code->co_firstlineno) {
+ PyErr_Format(PyExc_ValueError,
+ "line %d comes before the current code block",
+ new_lineno);
+ return -1;
+ }
+
+ /* Find the bytecode offset for the start of the given line, or the
+ * first code-owning line after it. */
+ PyString_AsStringAndSize(f->f_code->co_lnotab, &lnotab, &lnotab_len);
+ addr = 0;
+ line = f->f_code->co_firstlineno;
+ new_lasti = -1;
+ for (offset = 0; offset < lnotab_len; offset += 2) {
+ addr += lnotab[offset];
+ line += lnotab[offset+1];
+ if (line >= new_lineno) {
+ new_lasti = addr;
+ new_lineno = line;
+ break;
+ }
+ }
+
+ /* If we didn't reach the requested line, return an error. */
+ if (new_lasti == -1) {
+ PyErr_Format(PyExc_ValueError,
+ "line %d comes after the current code block",
+ new_lineno);
+ return -1;
+ }
+
+ /* We're now ready to look at the bytecode. */
+ PyString_AsStringAndSize(f->f_code->co_code, &code, &code_len);
+ min_addr = MIN(new_lasti, f->f_lasti);
+ max_addr = 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
+ * won't be true if you jump to them. They always start with code
+ * that either pops the exception using POP_TOP (plain 'except:'
+ * lines do this) or duplicates the exception on the stack using
+ * DUP_TOP (if there's an exception type specified). See compile.c,
+ * 'com_try_except' for the full details. There aren't any other
+ * cases (AFAIK) where a line's code can start with DUP_TOP or
+ * POP_TOP, but if any ever appear, they'll be subject to the same
+ * restriction (but with a different error message). */
+ if (code[new_lasti] == DUP_TOP || code[new_lasti] == POP_TOP) {
+ PyErr_SetString(PyExc_ValueError,
+ "can't jump to 'except' line as there's no exception");
+ return -1;
+ }
+
+ /* 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;
+ memset(blockstack, '\0', sizeof(blockstack));
+ memset(in_finally, '\0', sizeof(in_finally));
+ blockstack_top = 0;
+ for (addr = 0; addr < code_len; addr++) {
+ unsigned char op = code[addr];
+ switch (op) {
+ case SETUP_LOOP:
+ case SETUP_EXCEPT:
+ case SETUP_FINALLY:
+ blockstack[blockstack_top++] = addr;
+ in_finally[blockstack_top-1] = 0;
+ break;
+
+ case POP_BLOCK:
+ setup_op = code[blockstack[blockstack_top-1]];
+ if (setup_op == SETUP_FINALLY) {
+ in_finally[blockstack_top-1] = 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. */
+ setup_op = code[blockstack[blockstack_top-1]];
+ if (setup_op == SETUP_FINALLY) {
+ blockstack_top--;
+ }
+ 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;
+ }
+ }
+ }
+
+ if (op >= HAVE_ARGUMENT) {
+ addr += 2;
+ }
+ }
+
+ 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++) {
+ unsigned char op = code[addr];
+ switch (op) {
+ case SETUP_LOOP:
+ case SETUP_EXCEPT:
+ case SETUP_FINALLY:
+ delta_iblock++;
+ break;
+
+ case POP_BLOCK:
+ delta_iblock--;
+ break;
+ }
+
+ min_delta_iblock = MIN(min_delta_iblock, delta_iblock);
+
+ if (op >= HAVE_ARGUMENT) {
+ addr += 2;
+ }
+ }
+
+ /* 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. */
+ while (f->f_iblock > new_iblock) {
+ PyTryBlock *b = &f->f_blockstack[--f->f_iblock];
+ while ((f->f_stacktop - f->f_valuestack) > b->b_level) {
+ PyObject *v = (*--f->f_stacktop);
+ Py_DECREF(v);
+ }
+ }
+
+ /* Finally set the new f_lineno and f_lasti and return OK. */
+ f->f_lineno = new_lineno;
+ f->f_lasti = new_lasti;
+ return 0;
+}
+
static PyObject *
frame_gettrace(PyFrameObject *f, void *closure)
{
@@ -77,7 +334,8 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
static PyGetSetDef frame_getsetlist[] = {
{"f_locals", (getter)frame_getlocals, NULL, NULL},
- {"f_lineno", (getter)frame_getlineno, NULL, NULL},
+ {"f_lineno", (getter)frame_getlineno,
+ (setter)frame_setlineno, NULL},
{"f_trace", (getter)frame_gettrace, (setter)frame_settrace, NULL},
{0}
};