Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Lib/test/dtracedata/unencodable_names.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
python$target:::function-entry,
python$target:::function-return
/arg0 == 0 || arg1 == 0/
{
printf("%d\t%s:%d\n", timestamp, probename, arg2);
}

python$target:::import-find-load-start
/arg0 == 0/
{
printf("%d\t%s\n", timestamp, probename);
}

python$target:::import-find-load-done
/arg0 == 0/
{
printf("%d\t%s:%d\n", timestamp, probename, arg1);
}
6 changes: 6 additions & 0 deletions Lib/test/dtracedata/unencodable_names.d.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function-entry:1
function-return:2
function-entry:4
function-return:5
import-find-load-start
import-find-load-done:0
20 changes: 20 additions & 0 deletions Lib/test/dtracedata/unencodable_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
def function():
return 1

def error():
raise ValueError

# gh-156118: The probes receive NULL for names that cannot be encoded to
# UTF-8, and they must neither leak nor replace an exception.
function.__code__ = function.__code__.replace(co_name='\udc80')
error.__code__ = error.__code__.replace(co_filename='\udc80')

function()
try:
error()
except ValueError:
pass
try:
__import__('\udc80')
except ImportError:
pass
21 changes: 21 additions & 0 deletions Lib/test/dtracedata/unencodable_names.stp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry"),
@PYTHON_SYSTEMTAP_PROBE@("function__return")
{
if ($arg1 == 0 || $arg2 == 0) {
printf("%d\t%s:%d\n", gettimeofday_us(), $$name, $arg3);
}
}

probe @PYTHON_SYSTEMTAP_PROBE@("import__find__load__start")
{
if ($arg1 == 0) {
printf("%d\t%s\n", gettimeofday_us(), $$name);
}
}

probe @PYTHON_SYSTEMTAP_PROBE@("import__find__load__done")
{
if ($arg1 == 0) {
printf("%d\t%s:%d\n", gettimeofday_us(), $$name, $arg2);
}
}
6 changes: 6 additions & 0 deletions Lib/test/dtracedata/unencodable_names.stp.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function__entry:1
function__return:2
function__entry:4
function__return:5
import__find__load__start
import__find__load__done:0
23 changes: 23 additions & 0 deletions Lib/test/test_dtrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,20 @@ class BPFTraceBackend(TraceBackend):
}}
END {{ clear(@tracing); }}
""",
"unencodable_names": """
usdt:{python}:python:function__entry /arg0 == 0 || arg1 == 0/ {{
printf("%lld\\tfunction__entry:%d\\n", nsecs, arg2);
}}
usdt:{python}:python:function__return /arg0 == 0 || arg1 == 0/ {{
printf("%lld\\tfunction__return:%d\\n", nsecs, arg2);
}}
usdt:{python}:python:import__find__load__start /arg0 == 0/ {{
printf("%lld\\timport__find__load__start\\n", nsecs);
}}
usdt:{python}:python:import__find__load__done /arg0 == 0/ {{
printf("%lld\\timport__find__load__done:%d\\n", nsecs, arg1);
}}
""",
}

# Which test scripts to filter by filename (None = use @tracing flag)
Expand Down Expand Up @@ -352,6 +366,12 @@ def _filter_probe_rows(output):
gc__done:0
gc__start:2
gc__done:1""",
"unencodable_names": """function__entry:1
function__return:2
function__entry:4
function__return:5
import__find__load__start
import__find__load__done:0""",
}

def run_case(self, name, optimize_python=None):
Expand Down Expand Up @@ -494,6 +514,9 @@ def get_function_instructions(funcname):
def test_gc(self):
self.run_case("gc")

def test_unencodable_names(self):
self.run_case("unencodable_names")


class DTraceNormalTests(TraceTests, unittest.TestCase):
backend = DTraceBackend()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix a crash in debug builds, and a spurious :exc:`SystemError` or
:exc:`UnicodeEncodeError` in release builds, when a DTrace or SystemTap
probe fires for a module, function or file name that cannot be encoded to
UTF-8.
23 changes: 19 additions & 4 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,21 @@ int _PyEval_StoreName(PyThreadState *tstate, _PyStackRef v, PyObject *name, PyOb
#endif

#ifdef WITH_DTRACE
/* Return the UTF-8 encoding of name, or NULL if it cannot be encoded
(e.g. it contains a lone surrogate). Never sets an exception and keeps
the current one, if any, since it is used in the unwinding path. */
static const char *
dtrace_utf8(PyObject *name)
{
PyObject *exc = PyErr_GetRaisedException();
const char *utf8 = PyUnicode_AsUTF8(name);
if (utf8 == NULL) {
PyErr_Clear();
}
PyErr_SetRaisedException(exc);
return utf8;
}

static void
dtrace_function_entry(_PyInterpreterFrame *frame)
{
Expand All @@ -1204,8 +1219,8 @@ dtrace_function_entry(_PyInterpreterFrame *frame)
int lineno;

PyCodeObject *code = _PyFrame_GetCode(frame);
filename = PyUnicode_AsUTF8(code->co_filename);
funcname = PyUnicode_AsUTF8(code->co_name);
filename = dtrace_utf8(code->co_filename);
funcname = dtrace_utf8(code->co_name);
lineno = PyUnstable_InterpreterFrame_GetLine(frame);

PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
Expand All @@ -1219,8 +1234,8 @@ dtrace_function_return(_PyInterpreterFrame *frame)
int lineno;

PyCodeObject *code = _PyFrame_GetCode(frame);
filename = PyUnicode_AsUTF8(code->co_filename);
funcname = PyUnicode_AsUTF8(code->co_name);
filename = dtrace_utf8(code->co_filename);
funcname = dtrace_utf8(code->co_name);
lineno = PyUnstable_InterpreterFrame_GetLine(frame);

PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
Expand Down
18 changes: 16 additions & 2 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,20 @@ get_importtime_name(PyObject *name)
return encoded;
}

/* Return the UTF-8 encoding of name for a DTrace probe, or NULL if it cannot
be encoded. Never sets an exception and keeps the current one, if any. */
static const char *
get_dtrace_name(PyObject *name)
{
PyObject *exc = PyErr_GetRaisedException();
const char *utf8 = PyUnicode_AsUTF8(name);
if (utf8 == NULL) {
PyErr_Clear();
}
PyErr_SetRaisedException(exc);
return utf8;
}

static int
import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name)
{
Expand Down Expand Up @@ -4120,14 +4134,14 @@ import_find_and_load_with_name(PyThreadState *tstate, PyObject *abs_name,
}

if (PyDTrace_IMPORT_FIND_LOAD_START_ENABLED())
PyDTrace_IMPORT_FIND_LOAD_START(PyUnicode_AsUTF8(abs_name));
PyDTrace_IMPORT_FIND_LOAD_START(get_dtrace_name(abs_name));

mod = PyObject_CallMethodObjArgs(IMPORTLIB(interp), find_and_load,
abs_name, IMPORT_FUNC(interp), NULL);

if (PyDTrace_IMPORT_FIND_LOAD_DONE_ENABLED()) {
int found = mod != NULL && mod != not_found;
PyDTrace_IMPORT_FIND_LOAD_DONE(PyUnicode_AsUTF8(abs_name),
PyDTrace_IMPORT_FIND_LOAD_DONE(get_dtrace_name(abs_name),
found);
}

Expand Down
Loading