This is an archive of the discontinued LLVM Phabricator instance.

exception handling in PythonDataObjects.
ClosedPublic

Authored by lawrence_danna on Oct 5 2019, 5:23 PM.

Details

Summary

Python APIs nearly all can return an exception. They do this
by returning NULL, or -1, or some such value and setting
the exception state with PyErr_Set*(). Exceptions must be
handled before further python API functions are called. Failure
to do so will result in asserts on debug builds of python.
It will also sometimes, but not usually result in crashes of
release builds.

Nearly everything in PythonDataObjects.h needs to be updated
to account for this. This patch doesn't fix everything,
but it does introduce some new methods using Expected<>
return types that are safe to use.

split off from https://reviews.llvm.org/D68188

Event Timeline

lawrence_danna created this revision.Oct 5 2019, 5:23 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 5 2019, 5:23 PM
labath added a comment.Oct 7 2019, 8:10 AM

I like the direction this is going in. Some questions about the implementation/interface inline..

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
44–45

Please remove this. It kind of looks like this using declarations are local to this block, but they are not, and you are exposing these two new identifiers to anyone who happens to include this file. I am actually a proponent of importing these identifiers (and several others) into lldb_private, but that should be discussed separately. I started a discussion about that several months ago http://lists.llvm.org/pipermail/lldb-dev/2019-April/014978.html, but there wasn't universal consensus, so it kind of fizzled out.

Feel free to to restart that thread and/or to put these directives in a cpp file though.

104–110

What should be the behavior of these methods if obj is null? And what if obj is not of type T ? Right now, they will return an "empty" object, but this is the old behavior of the data objects classes (which was implemented back when we did not have Expected<T>), and I am not sure it is the right thing to do here. For the nullptr case, maybe we could handle that via an assertion (or even making these function take a PyObject& argument)? And a type mismatch seems like it could be best handled by returning an error instead of an empty object.

(Also, these functions shouldn't be static.)

104–122

Are there any cases where it is valid to "take" an object even if an exception has occured. Should we maybe move the "assert" into the previous two functions and delete this one? (From the name it does not seem too clear what is this function asserting, so it would be best to remove it altogether).

135

I think we should discourage people from passing these objects into the native APIs, so forcing them to use the existing get() methods seems sufficient to me.

259

explicit

280

Implemented like this, this method still requires one to work with python types directly, and deal with things (signatures) that is better left to the glue code. As this is c++, what do you think of an implementation like:

template<typename T, typename Enable = void> struct PythonFormat;
template<> struct PythonFormat<unsigned long long> {
    static constexpr char format = 'K';
    static auto get(unsigned long long value) { return value; }
};
template<typename T>
struct PythonFormat<T,
        typename std::enable_if<
            std::is_base_of<PythonObject ,T>::value>::type> {
    static constexpr char format = 'O';
    static auto get(const T &value) { return value.get(); }
};
// etc.

template<typename... T>
Expected<PythonObject> CallMethod(const char *name, const T &... t) {
    const char format[] = { '(', PythonEncoding<T>::format..., ')'};
    PyObject *obj = PyObject_CallMethod(m_py_obj, name, format, PythonFormat<T>::get(t)...);
    ...
}

This should make calling a python method as close to calling a native one as possible. The main downside of that is that it is impossible to use fancier formats like s#. If we really wanted to, we could make that work too (at the expense of a fairly large increase in template complexity), but it doesn't look like you need to call any fancy method now anyway.

313–321

Should this maybe be a specialization of AsType for T = long long ? That might reduce your desire for monads...

323

I guess the RHS should by a PythonObject too..

347

Are you sure this std::move actually does anything -- I see no applicable rvalue constructor

408

this is not needed. const char * is implicitly convertible to a StringRef

668

Add a comment about when should this be used. I guess it should be only done as the last thing before returning to python (?)

lawrence_danna marked 13 inline comments as done.

review fixes

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
280

oh, yea i like that idea.

347

It doesn't do anything yet, but it's conceptually correct and it will do something when refactor the PythonObject classes to get rid of the virtual methods.

Harbormaster completed remote builds in B39122: Diff 223691.
labath accepted this revision.Oct 8 2019, 1:51 AM

I am quite happy about how this is turning out to be. Thanks for taking your time to do this.

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
36–53

llvm style is to not put else after a return http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return. Also, for short statements like this, I'd omit braces. And lastly, in line with the "keep namespaces small" philosophy I think you should define these as template<> ... python::As<long long>(...) :)

1099–1103

no else after return, no braces

This revision is now accepted and ready to land.Oct 8 2019, 1:51 AM
lawrence_danna marked 2 inline comments as done.

style fixes

This revision was automatically updated to reflect the committed changes.