Teach searchfox about C++ promise chaining control flow especially PromiseNativeHandler (which does not accidentally leverage lambdas)
Categories
(Webtools :: Searchfox, enhancement)
Tracking
(Not tracked)
People
(Reporter: asuth, Unassigned)
References
Details
Attachments
(1 file)
(deleted),
image/png
|
Details |
Promises provide a means of indirect control flow. A method M can return a promise P which it will resolve in the future, and the caller C of that function invokes a method on P that will result in handler H being invoked when M eventually resolves P. In cases where std::function lambdas are used, searchfox is frequently able to incidentally handle this because the contents of the lambda are fundamentally fused with the method in which they're defined.
So a C++ method calling (simplified pseudocode) CacheAPI::Put and directly chaining a Then lambda that calls this->ProcessPutResults will be a little odd because the edges should really be C -> M -> H;
but we end up with C -> M; C -> H;
. (Note, however that there's a very lucky (and weird) nuance here discovered in bug 1779340 where inside argument lists to call a function like M we will see M as the context for the arguments. So explicit callbacks may accidentally look right, but I haven't fully investigated the full ramifications of this especially in the face of templates. It would not be shocking if this resulted in very weird regressions in the future as we improve template handling.)
There's a systemic issue here, but to make this concrete/actionable, PromiseNativeHandler is an example where searchfox definitely is not already doing the right thing here but where we could do better and it could be very valuable. (A non-promise variation of this is using explicitly named runnables as a means of async execution. The caller gets an edge to the class and there's a hidden edge to the class'es overridden "Run" method. That's similar to how there should be hidden edges for AppendNativeHandler to ResolvedCallback and RejectedCallback if they exist, or NativeHandlerCallback if they don't.)
Conceptually this is a little bit like DoctorJS' "abstract interpretation" although our approach I think will very much be more heuristic-based.
Note that there would basically be 2 levels of implementation one could imagine here:
- Semantic linkage magic in bug 1727789 handles the the immediate local case of "I just saw a method call return a promise and now we're calling a method on that promise and I know how to add a synthetic edge there (which will also suppress the native edge) between the called method and the handler thing we're adding.
- We do a more elegant modeling that's more dataflow aware so if the promise is stored (in a holder) on an object and a method returns a pre-existing promise, we can potentially have the chain understand the sets of where it's potentially created, where it's potentially resolved/rejected, and what potentially listens to the resolve/reject.
There's a lot to think about in terms of how to model this. I think a reasonable approach is probably to:
- Explicitly recognize promises as a first-class abstraction
- Avoid collapsing chained promises into a single union, but make sure presentation UI knows how to elide meaningless temporaries, etc. This should avoid the "everything got unioned together and now nothing is useful" problem.
- Make sure the bug 1727789 related slot modeling enhancements from bug 1776522 land first, as that should probably let us support treating the promise abstractions as executable-ish.
- Add explicit cmd_traverse support for promise related edges.
- Prototype with a fairly primitive TOML-based mechanism for annotating the semantics onto our C++ promise implementations so we can avoid getting into processing doxygen markup, etc. We'll probably want this anyways for repositories like wubkat where we can maybe add some semantics but we're unlikely to commit anything into the underlying source tree.
Description
•