labath added inline comments.
================ Comment at: lldb/include/lldb/Interpreter/ScriptInterpreter.h:469 + + virtual int GetNumArgumentsForCallable(const char *callable_name) { + return -1; ---------------- jingham wrote: > jingham wrote: > > labath wrote: > > > labath wrote: > > > > lawrence_danna wrote: > > > > > jingham wrote: > > > > > > lawrence_danna wrote: > > > > > > > labath wrote: > > > > > > > > jingham wrote: > > > > > > > > > lawrence_danna wrote: > > > > > > > > > > labath wrote: > > > > > > > > > > > In light of varargs functions (`*args, **kwargs`), which > > > > > > > > > > > are fairly popular in python, the concept of "number of > > > > > > > > > > > arguments of a callable" does not seem that well defined. > > > > > > > > > > > The current implementation seems to return the number of > > > > > > > > > > > fixed arguments, which might be fine, but I think this > > > > > > > > > > > behavior should be documented. Also, it would be good to > > > > > > > > > > > modernize this function signature -- have it take a > > > > > > > > > > > StringRef, and return a `Expected<unsigned (?)>` -- > > > > > > > > > > > ongoing work by @lawrence_danna will make it possible to > > > > > > > > > > > return errors from the python interpreter, and this will > > > > > > > > > > > make it possible to display those, instead of just > > > > > > > > > > > guessing that this is because the callable was not found > > > > > > > > > > > (it could in fact be because the named thing is not a > > > > > > > > > > > callable, of because resolving the name produced an > > > > > > > > > > > exception, ...). > > > > > > > > > > I just took a look at PythonCallable::GetNumArguments() and > > > > > > > > > > it's horribly broken. > > > > > > > > > > > > > > > > > > > > It doesn't even work for the simplest test case I could > > > > > > > > > > think of. > > > > > > > > > > > > > > > > > > > > ``` > > > > > > > > > > auto builtins = PythonModule::Import("builtins"); > > > > > > > > > > ASSERT_THAT_EXPECTED(builtins, llvm::Succeeded()); > > > > > > > > > > auto hex = > > > > > > > > > > As<PythonCallable>(builtins.get().GetAttribute("hex")); > > > > > > > > > > ASSERT_THAT_EXPECTED(hex, llvm::Succeeded()); > > > > > > > > > > EXPECT_EQ(hex.get().GetNumArguments().count, 1u); > > > > > > > > > > ``` > > > > > > > > > > > > > > > > > > > > we should really re-write it to use inspect.signature. > > > > > > > > > Interesting. We use it for free functions (what you pass to > > > > > > > > > the -F option of `breakpoint command add`) and for the > > > > > > > > > __init__ and __call__ method in the little classes you can > > > > > > > > > make up for scripted thread plans and for the class version > > > > > > > > > of Python implemented command-line commands. We have tests > > > > > > > > > for telling 3 vrs. 4 vrs. not enough or too many, and they > > > > > > > > > all pass. So it seems to work in the cases we currently need > > > > > > > > > it to work for... > > > > > > > > > > > > > > > > > > "inspect.signature" is python 3 only, and the python 2 > > > > > > > > > equivalent is deprecated. So it will take a little fancy > > > > > > > > > footwork to use it in the transition period. > > > > > > > > lol :) > > > > > > > > > > > > > > > > I would actually say that we should try not to use this > > > > > > > > function(ality) wherever possible. Making decisions based on > > > > > > > > the number of arguments the thing you're about to call takes > > > > > > > > sounds weird. I don't want to get too involved in this, but I > > > > > > > > was designing this, I'd just say that if one tries to pass > > > > > > > > arguments to the callback then the callback MUST take three > > > > > > > > arguments (or we'll abort processing the breakpoint command). > > > > > > > > If he wants his function to be called both with arguments and > > > > > > > > without, he can just add a default value to the third argument. > > > > > > > > (And if his function takes two arguments, but he still tries to > > > > > > > > pass something... well, it's his own fault). > > > > > > > > > > > > > > > > Anyway, feel free to ignore this comment, but I felt like I had > > > > > > > > to say something. :) > > > > > > > I completely agree with Pavel. Inspecting a function signature > > > > > > > before calling it is a big code smell in python. If there's a > > > > > > > way to avoid doing that introspection, that would be better. > > > > > > Unfortunately, we originally designed this interface to take three > > > > > > arguments, the frame pointer, the breakpoint location and the > > > > > > Python session dict. Then it became clear that it would be better > > > > > > to add this extra args argument (and in the case of Python based > > > > > > commands the ExecutionContext pointer). At that point we had three > > > > > > choices, abandon the improvement; switch to unconditionally passing > > > > > > the extra arguments, and break everybody's extant uses; or switch > > > > > > off of the number of arguments to decide whether the user had > > > > > > provided the old or new forms. > > > > > > > > > > > > My feeling about lldb Python scripts/commands etc. that people use > > > > > > in the debugger is that a lot of users don't know how they work at > > > > > > all, they just got them from somebody else; and many more figured > > > > > > out how to write them for some specific purpose, and then pretty > > > > > > much forgot how they worked. So suddenly breaking all these bits > > > > > > of functionality will result in folks just deciding that this > > > > > > affordance is not reliable and not worth their time, which would be > > > > > > a shame. > > > > > > > > > > > > So instead we accommodate both forms, which requires that we know > > > > > > which one the user provided. If you see a better way to do this, > > > > > > (and are willing to implement it because so far as I can see this > > > > > > method is going to work just fine) dig in, I'm not wedded to the > > > > > > particular approach. But I am not excited about penalizing our > > > > > > users because we didn't get the API design right the first time > > > > > > through. > > > > > makes sense. > > > > > > > > > > The only other way I can think of to solve it would be to have some > > > > > indication in the `break com add` command of what signature it > > > > > expects from the function. But that's really ugly too because now > > > > > you're asking users to understand yet another option. > > > > > > > > > > I put up https://reviews.llvm.org/D68995 this morning which adds > > > > > `inspect.signature` support for pythons that have it. > > > > > > > > > > Currently we have really gnarly ArgInfo tests like this: > > > > > ``` > > > > > if (argc.count == 5 || argc.is_bound_method || argc.has_varargs) > > > > > pfunc(debugger_arg, PythonString(args), exe_ctx_arg, > > > > > cmd_retobj_arg, dict); > > > > > else > > > > > pfunc(debugger_arg, PythonString(args), cmd_retobj_arg, dict); > > > > > ``` > > > > > > > > > > 😖 > > > > > > > > > > I think if we replace `count` with `max_positional_args` we should > > > > > be able to replace that kindof test with just > > > > > ``` > > > > > if (argc.max_positional_args < 5) > > > > > old_version > > > > > else > > > > > new_version > > > > > ``` > > > > > > > > > > > > > > So, how about this: Put extra_args as the last argument, instead of > > > > inserting it in the middle (so the new signature becomes `frame, > > > > bp_loc, dict, extra_args` instead of `frame, bp_loc, extra_args, dict`. > > > > Then instead of > > > > ``` > > > > if (arg_info.count == 3) > > > > result = pfunc(frame_arg, bp_loc_arg, dict); > > > > else if (arg_info.count == 4) { > > > > lldb::SBStructuredData *args_value = new > > > > lldb::SBStructuredData(args_impl); > > > > PythonObject args_arg(PyRefType::Owned, > > > > SBTypeToSWIGWrapper(args_value)); > > > > result = pfunc(frame_arg, bp_loc_arg, args_arg, dict); > > > > ``` > > > > we do: > > > > ``` > > > > if (args_impl.was_specified()) > > > > pfunc(frame_arg, bp_loc_arg, dict, args_arg) > > > > else > > > > pfunc(frame_arg, bp_loc_arg, dict); > > > > ``` > > > > All existing scripts will not specify the extra arguments, so they will > > > > work as usual. New scripts which do pass extra arguments will have to > > > > use the new signature. New scripts can also put `args = None` in their > > > > python signature, so that they are callable both with and without > > > > arguments, should they so desire. (If we don't want to support the > > > > `=None` use case then we can even keep the arguments in the same order > > > > as in this patch.) > > > > > > > > Is there some reason why that would not work? > > > > The only other way I can think of to solve it would be to have some > > > > indication in the break com add command of what signature it expects > > > > from the function. > > > > > > That's kind of what I'm getting at. I am hoping that the presence of the > > > `--key`, `--value` options can be used as an indicator of that signature. > > > (Though maybe I am misunderstanding something and I should shut up.) > > The is_bound_method test is cheesy. We didn't offer a "class with > > __call__" for Python based commands until after we added the > > ExecutionContext argument, so this check knows that if you are providing a > > class method, then you are probably also providing the correct number of > > arguments. max_positional_args seems a more explicit approach. > > > > I think the varargs check is to allow you to write a command that takes the > > old three arguments and the ExecutionContext as a vararg, so the same > > Python function could work with an older lldb that didn't send the exe_ctx > > but take advantage of the better interface if it was present. After all, > > the fallback of using the currently selected "target/process/thread/frame" > > inside the function will mostly work. > > > > For the affordances taking the "extra_args", like breakpoint commands and > > scripted breakpoints and the like, it's hard to see how you could have a > > reasonable fallback to "you didn't tell me what function to look for..." > > So for these I want to count the fixed arguments, I don't think it is > > necessary to allow them to be passed as varargs. > > > > Thanks for fixing up the PythonObjects code, BTW! > I did consider switching off of whether the user provided key & value > arguments. But that would mean that you could not write Python breakpoint > command functions that have default behaviors, but will do special things if > the user provided `--key` and `--value` arguments. That seemed like a > reasonable thing to support, so I switched to doing everything based on the > actual signature we were provided. Why not? If the last argument has a default value (`def my_fancy_callback(frame, bp_loc, dict, extra_args = None):`), then it should be callable both with three and four arguments, should it not? I think the `=None` part actually nicely documents that the callback is usable both with extra arguments, but also has a reasonable default behavior. Repository: rLLDB LLDB CHANGES SINCE LAST ACTION https://reviews.llvm.org/D68671/new/ https://reviews.llvm.org/D68671 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits