Skip to content

[3.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace events#5928

Merged
serhiy-storchaka merged 4 commits into
python:3.7from
xdegaye:bpo-17288
Mar 13, 2018
Merged

[3.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace events#5928
serhiy-storchaka merged 4 commits into
python:3.7from
xdegaye:bpo-17288

Conversation

@xdegaye

@xdegaye xdegaye commented Feb 27, 2018

Copy link
Copy Markdown
Contributor

Comment thread Lib/test/test_sys_settrace.py Outdated
sys.settrace(None)
self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])

def test_no_jump_from_call(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could tests use the jump_test() decorator?

Comment thread Objects/frameobject.c Outdated
* Forbidding jumps from the 'call' event of a new frame is a side effect
* of allowing to set f_lineno only from trace functions. */
if (f->f_lasti == -1)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please left this brace on the same line as if to conform PEP 7.

Comment thread Objects/frameobject.c
/* The trace function is called with a 'return' trace event after the
* execution of a yield statement. */
assert(f->f_lasti != -1);
if (code[f->f_lasti] == YIELD_VALUE || code[f->f_lasti] == YIELD_FROM) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to check for RETURN_VALUE as in your original patch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only function called by _PyEval_EvalFrameDefault() that may allow a jump to another line is maybe_call_line_trace() because stack_pointer is saved on f->f_stacktop before the call and there is a JUMPTO(f->f_lasti) after the call. All the other trace functions called within _PyEval_EvalFrameDefault() are called with f->f_stacktop == NULL except after the handling of the YIELD_FROM and YIELD_VALUE opcodes. So by forbidding jumps in frame_setlineno() when f->f_stacktop == NULL we handle all the cases, including the RETURN_VALUE opcode, except the yield statements. The check for RETURN_VALUE in my original patch was overkill.

Fix also a bug in the previous implementation of the PR where
jumpFrom/jumpTo were systematically incremented by 1 by the
jump_test() decorator without taking into account that
JumpTracer allows now jumpFrom/jumpTo from within nested
functions.
@serhiy-storchaka

Copy link
Copy Markdown
Member

Thank you @xdegaye! This is very nice PR. I like how it fixes the non-trivial bug. I have learned something new when reading it.

But I don't like the complexity of the new implementation of the trace function and the fact that it breaks one existing test. This code looks complicated and fragile. I afraid that it will be hard to modify it for supporting new tests or fix it if some details of frames or code objects will be changed. If in some implementation list.append() is implemented in Python this will break tests since the tracer will be confused by new frames.

I suggest to use the following implementation:

    def trace(self, frame, event, arg):
        if self.done:
            return
        if (self.firstLine is None and frame.f_code == self.code and
                event == 'line'):
            self.firstLine = frame.f_lineno - 1
        if (event == self.event and self.firstLine and
                frame.f_lineno == self.firstLine + self.jumpFrom):
            f = frame
            while f is not None and f.f_code != self.code:
                f = f.f_back
            if f is not None:
                try:
                    frame.f_lineno = self.firstLine + self.jumpTo
                except TypeError:
                    frame.f_lineno = self.jumpTo
                self.done = True
        return self.trace

and in the constructor:

        self.firstLine = None if decorated else self.code.co_firstlineno

test_jump_forwards_out_of_with_block can be restored, and two new test should be written as:

    @jump_test(2, 3, [1], event='call', error=(ValueError, "can't jump from"
               " the 'call' trace event of a new frame"))
    def test_no_jump_from_call(output):
        output.append(1)
        def nested():
            output.append(3)
        nested()
        output.append(5)

    @jump_test(3, 2, [2], event='return', error=(ValueError,
               "can't jump from a yield statement"))
    def test_no_jump_from_yield(output):
        def gen():
            output.append(2)
            yield 3
        next(gen())
        output.append(5)

Line numbers are always relative to the first line of the testing function which is determined by the first executed line if decorated is true.

@xdegaye

xdegaye commented Mar 12, 2018

Copy link
Copy Markdown
Contributor Author

@serhiy-storchaka thanks, this is so much simpler. I have updated the PR with your suggestion.

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @xdegaye!

I have removed comments for brevity in the example. Could you please restore the old comment about non-integer jumpTo? Or maybe even add new comments to non-trivial code?

@xdegaye

xdegaye commented Mar 12, 2018

Copy link
Copy Markdown
Contributor Author

@serhiy-storchaka comments have been restored in the last commit.

@serhiy-storchaka serhiy-storchaka merged commit e32bbaf into python:3.7 Mar 13, 2018
@serhiy-storchaka serhiy-storchaka added type-bug An unexpected behavior, bug, or error needs backport to 3.6 labels Mar 13, 2018
@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @xdegaye for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@serhiy-storchaka

Copy link
Copy Markdown
Member

Great! Could you please create a backport to master?

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Mar 13, 2018
…ents. (pythonGH-5928)

(cherry picked from commit e32bbaf)

Co-authored-by: xdegaye <xdegaye@gmail.com>
@bedevere-bot

Copy link
Copy Markdown

GH-6100 is a backport of this pull request to the 3.6 branch.

@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @xdegaye for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 2.7.
🐍🍒⛏🤖

@miss-islington

Copy link
Copy Markdown
Contributor

Sorry, @xdegaye and @serhiy-storchaka, I could not cleanly backport this to 2.7 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker e32bbaf376a09c149fa7c7f2919d7c9ce4e2a055 2.7

miss-islington added a commit that referenced this pull request Mar 13, 2018
…ents. (GH-5928)

(cherry picked from commit e32bbaf)

Co-authored-by: xdegaye <xdegaye@gmail.com>
@xdegaye xdegaye deleted the bpo-17288 branch March 13, 2018 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-bug An unexpected behavior, bug, or error

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants