Skip to content

Commit

Permalink
fix(internal): closure bytecode wrapping for Python 3.11 [backport 2.…
Browse files Browse the repository at this point in the history
…0] (#7052)

Backport 9bd1fb9 from #7044 to 2.0.

We fix the bytecode wrapping for closures to ensure that the frame
objects don't end up referencing missing data, ultimately resulting in a
segmentation fault in profiling tools.

Fixes #6701.

## Checklist

- [x] Change(s) are motivated and described in the PR description.
- [x] Testing strategy is described if automated tests are not included
in the PR.
- [x] Risk is outlined (performance impact, potential for breakage,
maintainability, etc).
- [x] Change is maintainable (easy to change, telemetry, documentation).
- [x] [Library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
are followed. If no release note is required, add label
`changelog/no-changelog`.
- [x] Documentation is included (in-code, generated user docs, [public
corp docs](https://github.com/DataDog/documentation/)).
- [x] Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist

- [x] Title is accurate.
- [x] No unnecessary changes are introduced.
- [x] Description motivates each change.
- [x] Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes unless absolutely necessary.
- [x] Testing strategy adequately addresses listed risk(s).
- [x] Change is maintainable (easy to change, telemetry, documentation).
- [x] Release note makes sense to a user of the library.
- [x] Reviewer has explicitly acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment.
- [x] Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
- [x] If this PR touches code that signs or publishes builds or
packages, or handles credentials of any kind, I've requested a review
from `@DataDog/security-design-and-guidance`.
- [x] This PR doesn't touch any of that.

Co-authored-by: Gabriele N. Tornetta <P403n1x87@users.noreply.github.com>
  • Loading branch information
github-actions[bot] and P403n1x87 authored Sep 26, 2023
1 parent d345d35 commit 5e48006
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 2 deletions.
21 changes: 19 additions & 2 deletions ddtrace/internal/wrapping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,30 @@ def wrap_bytecode(wrapper, wrapped):
Instr("LOAD_CONST", wrapped, lineno=lineno),
]
if PY >= (3, 11):
# THis is required to start a new frame
# From insert_prefix_instructions
instrs[0:0] = [
Instr("RESUME", 0, lineno=lineno - 1),
Instr("PUSH_NULL", lineno=lineno),
]

if code.co_cellvars:
from bytecode import CellVar

instrs[0:0] = [Instr("MAKE_CELL", CellVar(_), lineno=lineno) for _ in code.co_cellvars]

if code.co_freevars:
instrs.insert(0, Instr("COPY_FREE_VARS", len(code.co_freevars), lineno=lineno))

# Build the tuple of all the positional arguments
if nargs:
instrs.extend([Instr("LOAD_FAST", argname, lineno=lineno) for argname in argnames])
instrs.extend(
[
Instr("LOAD_DEREF", CellVar(argname), lineno=lineno)
if PY >= (3, 11) and argname in code.co_cellvars
else Instr("LOAD_FAST", argname, lineno=lineno)
for argname in argnames
]
)
instrs.append(Instr("BUILD_TUPLE", nargs, lineno=lineno))
if varargs:
instrs.extend(
Expand Down Expand Up @@ -213,6 +228,8 @@ def wrap(f, wrapper):

code = wrap_bytecode(wrapper, wrapped)
code.freevars = f.__code__.co_freevars
if PY >= (3, 11):
code.cellvars = f.__code__.co_cellvars
code.name = f.__code__.co_name
code.filename = f.__code__.co_filename
code.flags = f.__code__.co_flags
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fix an issue that could have caused some tracing integrations to create
invalid references to objects in Python frames, ultimately causing profiling
tools to potentially induce a segmentation fault.
24 changes: 24 additions & 0 deletions tests/internal/test_wrapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,27 @@ async def g():
await gen.aclose()

assert channel == [True] + [0, ValueError, 1] * 10 + ["GeneratorExit"]


def test_wrap_closure():
channel = []

def wrapper(f, args, kwargs):
channel.append((args, kwargs))
retval = f(*args, **kwargs)
channel.append(retval)
return retval

def outer(answer=42):
def f(a, b, c=None):
return (a, b, c, answer)

return f

wrap(outer, wrapper)

closure = outer()
wrap(closure, wrapper)

assert closure(1, 2, 3) == (1, 2, 3, 42)
assert channel == [((42,), {}), closure, ((1, 2, 3), {}), (1, 2, 3, 42)]

0 comments on commit 5e48006

Please sign in to comment.