[lldb] Add frame recognizers for libc++ std::invoke#105695
[lldb] Add frame recognizers for libc++ std::invoke#105695vogelsgesang merged 7 commits intollvm:mainfrom
std::invoke#105695Conversation
|
@llvm/pr-subscribers-lldb Author: Adrian Vogelsgesang (vogelsgesang) ChangesWith this commit, we also hide the implementation details of The regular expression passed into the Instead, I added support for By hiding the implementation details of Full diff: https://github.com/llvm/llvm-project/pull/105695.diff 8 Files Affected:
diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h
index 8acebc12c4b1dc..585f0b0bb59009 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -107,12 +107,14 @@ class StackFrameRecognizerManager {
public:
void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
ConstString module, llvm::ArrayRef<ConstString> symbols,
- bool first_instruction_only = true);
+ bool first_instruction_only = true,
+ Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled);
void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
lldb::RegularExpressionSP module,
lldb::RegularExpressionSP symbol,
- bool first_instruction_only = true);
+ bool first_instruction_only = true,
+ Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled);
void ForEach(std::function<
void(uint32_t recognizer_id, std::string recognizer_name,
@@ -143,10 +145,13 @@ class StackFrameRecognizerManager {
std::vector<ConstString> symbols;
lldb::RegularExpressionSP symbol_regexp;
bool first_instruction_only;
+ Mangled::NamePreference mangling_preference;
};
std::deque<RegisteredEntry> m_recognizers;
uint16_t m_generation;
+ std::unordered_set<Mangled::NamePreference> m_used_manglings;
+
};
/// \class ValueObjectRecognizerSynthesizedValue
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 9c4dbed6939ba9..df906e9d7c808f 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1049,7 +1049,7 @@ let Command = "thread backtrace" in {
def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
def thread_backtrace_unfiltered : Option<"unfiltered", "u">, Group<1>,
- Desc<"Filter out frames according to installed frame recognizers">;
+ Desc<"Do not filter out frames according to installed frame recognizers">;
}
let Command = "thread step scope" in {
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index c60200ab186d09..3665e1a4c77e55 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include <cstring>
+#include <iostream>
#include <memory>
@@ -44,7 +45,7 @@ char CPPLanguageRuntime::ID = 0;
/// A frame recognizer that is installed to hide libc++ implementation
/// details from the backtrace.
class LibCXXFrameRecognizer : public StackFrameRecognizer {
- RegularExpression m_hidden_function_regex;
+ std::array<RegularExpression, 3> m_hidden_regex;
RecognizedStackFrameSP m_hidden_frame;
struct LibCXXHiddenFrame : public RecognizedStackFrame {
@@ -53,10 +54,32 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
public:
LibCXXFrameRecognizer()
- : m_hidden_function_regex(
- R"(^std::__1::(__function.*::operator\(\)|__invoke))"
- R"((\[.*\])?)" // ABI tag.
- R"(( const)?$)"), // const.
+ : m_hidden_regex{
+ // internal implementation details of std::function
+ // std::__1::__function::__alloc_func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator()[abi:ne200000]
+ // std::__1::__function::__func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator()
+ // std::__1::__function::__value_func<void ()>::operator()[abi:ne200000]() const
+ RegularExpression{""
+ R"(^std::__[0-9]*::)" // Namespace.
+ R"(__function::.*::operator\(\))"
+ R"((\[.*\])?)" // ABI tag.
+ R"(( const)?$)"}, // const.
+ // internal implementation details of std::invoke
+ // std::__1::__invoke[abi:ne200000]<void (*&)()>
+ RegularExpression{
+ R"(^std::__[0-9]*::)" // Namespace.
+ R"(__invoke)"
+ R"((\[.*\])?)" // ABI tag.
+ R"(<.*>)"}, // template argument.
+ // internal implementation details of std::invoke
+ // std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne200000]<void (*&)()>
+ RegularExpression{
+ R"(^std::__[0-9]*::)" // Namespace.
+ R"(__invoke_void_return_wrapper<.*>::__call)"
+ R"((\[.*\])?)" // ABI tag.
+ R"(<.*>)"} // template argument.
+
+ },
m_hidden_frame(new LibCXXHiddenFrame()) {}
std::string GetName() override { return "libc++ frame recognizer"; }
@@ -69,8 +92,9 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
if (!sc.function)
return {};
- if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments()))
- return m_hidden_frame;
+ for (RegularExpression &r : m_hidden_regex)
+ if (r.Execute(sc.function->GetNameNoArguments()))
+ return m_hidden_frame;
return {};
}
@@ -81,8 +105,9 @@ CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
if (process)
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
- std::make_shared<RegularExpression>("^std::__1::"),
- /*first_instruction_only*/ false);
+ std::make_shared<RegularExpression>("std::__[0-9]*::"),
+ /*first_instruction_only=*/ false,
+ /*mangling_preference=*/ Mangled::ePreferDemangledWithoutArguments);
}
bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
@@ -108,8 +133,7 @@ bool contains_lambda_identifier(llvm::StringRef &str_ref) {
CPPLanguageRuntime::LibCppStdFunctionCallableInfo
line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol,
- llvm::StringRef first_template_param_sref,
- bool has_invoke) {
+ llvm::StringRef first_template_param_sref, bool has_invoke) {
CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info;
@@ -190,7 +214,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
ValueObjectSP sub_member_f_(member_f_->GetChildMemberWithName("__f_"));
if (sub_member_f_)
- member_f_ = sub_member_f_;
+ member_f_ = sub_member_f_;
}
if (!member_f_)
diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp
index 44411afc65dda9..37901a2ea526a8 100644
--- a/lldb/source/Target/StackFrameRecognizer.cpp
+++ b/lldb/source/Target/StackFrameRecognizer.cpp
@@ -62,19 +62,24 @@ void StackFrameRecognizerManager::BumpGeneration() {
void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, ConstString module,
- llvm::ArrayRef<ConstString> symbols, bool first_instruction_only) {
+ llvm::ArrayRef<ConstString> symbols, bool first_instruction_only,
+ Mangled::NamePreference mangling_preference) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false,
module, RegularExpressionSP(), symbols,
RegularExpressionSP(), first_instruction_only});
+ m_used_manglings.insert(mangling_preference);
BumpGeneration();
}
void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, RegularExpressionSP module,
- RegularExpressionSP symbol, bool first_instruction_only) {
+ RegularExpressionSP symbol, bool first_instruction_only,
+ Mangled::NamePreference mangling_preference) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true,
ConstString(), module, std::vector<ConstString>(),
- symbol, first_instruction_only});
+ symbol, first_instruction_only,
+ mangling_preference});
+ m_used_manglings.insert(mangling_preference);
BumpGeneration();
}
@@ -119,13 +124,30 @@ bool StackFrameRecognizerManager::RemoveRecognizerWithID(
void StackFrameRecognizerManager::RemoveAllRecognizers() {
BumpGeneration();
m_recognizers.clear();
+ m_used_manglings.clear();
}
StackFrameRecognizerSP
StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
const SymbolContext &symctx = frame->GetSymbolContext(
eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol);
- ConstString function_name = symctx.GetFunctionName();
+ ConstString function_name_mangled;
+ ConstString function_name_demangled;
+ ConstString function_name_noargs;
+ for (Mangled::NamePreference m : m_used_manglings) {
+ switch (m) {
+ case Mangled::ePreferMangled:
+ function_name_mangled = symctx.GetFunctionName(m);
+ break;
+ case Mangled::ePreferDemangled:
+ function_name_demangled = symctx.GetFunctionName(m);
+ break;
+ case Mangled::ePreferDemangledWithoutArguments:
+ function_name_noargs = symctx.GetFunctionName(m);
+ break;
+ }
+ }
+
ModuleSP module_sp = symctx.module_sp;
if (!module_sp)
return StackFrameRecognizerSP();
@@ -145,6 +167,17 @@ StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
if (!entry.module_regexp->Execute(module_name.GetStringRef()))
continue;
+ ConstString function_name = [&]() {
+ switch (entry.mangling_preference) {
+ case Mangled::ePreferMangled:
+ return function_name_mangled;
+ case Mangled::ePreferDemangled:
+ return function_name_demangled;
+ case Mangled::ePreferDemangledWithoutArguments:
+ return function_name_noargs;
+ }
+ }();
+
if (!entry.symbols.empty())
if (!llvm::is_contained(entry.symbols, function_name))
continue;
diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
index 30fe3ecb1e4bf4..28004194dee07c 100644
--- a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
+++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
@@ -7,6 +7,23 @@
class LibCxxStdFunctionRecognizerTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
+ @add_test_categories(["libc++"])
+ def test_frame_recognizer(self):
+ """Test that std::function all implementation details are hidden in SBFrame"""
+ self.build()
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.cpp")
+ )
+ self.assertIn("foo", thread.GetFrameAtIndex(0).GetFunctionName())
+ # Skip all hidden frames
+ frame_id = 1
+ while frame_id < thread.GetNumFrames() and thread.GetFrameAtIndex(frame_id).IsHidden():
+ frame_id = frame_id + 1
+ # Expect `std::function<...>::operator()` to be the direct parent of `foo`
+ self.assertIn("::operator()", thread.GetFrameAtIndex(frame_id).GetFunctionName())
+ # And right above that, there should be the `main` frame
+ self.assertIn("main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName())
+
@add_test_categories(["libc++"])
def test_backtrace(self):
"""Test that std::function implementation details are hidden in bt"""
@@ -27,12 +44,12 @@ def test_backtrace(self):
self.expect(
"thread backtrace -u",
ordered=True,
- patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+ patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"],
)
self.expect(
"thread backtrace --unfiltered",
ordered=True,
- patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+ patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"],
)
@add_test_categories(["libc++"])
diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile b/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile
new file mode 100644
index 00000000000000..69014eb9c0f2eb
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile
@@ -0,0 +1,5 @@
+CXX_SOURCES := main.cpp
+USE_LIBCPP := 1
+CXXFLAGS_EXTRAS := -std=c++17
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py b/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py
new file mode 100644
index 00000000000000..f2f8c506630eac
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py
@@ -0,0 +1,28 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class LibCxxStdFunctionRecognizerTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ @add_test_categories(["libc++"])
+ def test_frame_recognizer(self):
+ """Test that implementation details details of `std::invoke`"""
+ self.build()
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ while process.GetState() != lldb.eStateExited:
+ self.assertIn("print_num", thread.GetFrameAtIndex(0).GetFunctionName())
+ self.process.Continue()
+ # # Skip all hidden frames
+ # frame_id = 1
+ # while frame_id < thread.GetNumFrames() and thread.GetFrameAtIndex(frame_id).IsHidden():
+ # frame_id = frame_id + 1
+ # # Expect `std::function<...>::operator()` to be the direct parent of `foo`
+ # self.assertIn("::operator()", thread.GetFrameAtIndex(frame_id).GetFunctionName())
+ # # And right above that, there should be the `main` frame
+ # self.assertIn("main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName())
diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp b/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp
new file mode 100644
index 00000000000000..78497d2938fe8a
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp
@@ -0,0 +1,40 @@
+#include <functional>
+#include <iostream>
+
+void print_num(int i) {
+ // break here
+ std::cout << i << '\n';
+}
+
+int add(int i, int j) {
+ // break here
+ return i + j;
+}
+
+struct PrintAdder {
+ PrintAdder(int num) : num_(num) {}
+ void operator()(int i) const {
+ // break here
+ std::cout << i << '\n';
+ }
+ void print_add(int i) const {
+ // break here
+ std::cout << num_ + i << '\n';
+ }
+ int num_;
+};
+
+int main() {
+ // Invoke a void-returning function
+ std::invoke(print_num, -9);
+
+ // Invoke a non-void-returning function
+ std::cout << std::invoke(add, 1, 10) << '\n';
+
+ // Invoke a member function
+ const PrintAdder foo(314159);
+ std::invoke(&PrintAdder::print_add, foo, 1);
+
+ // Invoke a function object
+ std::invoke(PrintAdder(12), 18);
+}
|
std::invokestd::invoke
|
@mordante @ldionne FYI. I would be interested which other functions come to mind that should be hidden. See #105457 (comment) for screenshots of how this looks from a user's perspective |
1836531 to
c7cc7e9
Compare
| // std::__1::__invoke[abi:ne200000]<void (*&)()> | ||
| RegularExpression{ | ||
| R"(^std::__[0-9]*::)" // Namespace. | ||
| R"(__invoke)" |
There was a problem hiding this comment.
I wonder if we should simply hide everything starting with ^std::__[0-9]*::__.*. Or are there any __ functions in libc++ which are not implementation details and should be shown in stacktraces by default?
There was a problem hiding this comment.
Agreed. Don't think we want to maintain this list.
(also, side-note, I think matching on const and abi_tag is probably redundant. Not a concern if we just filter out all the std::__*::__ stuff).
There was a problem hiding this comment.
I would like to defer switching to ^std::__[0-9]*::__.* in a follow-up commit, if that's fine by you.
I am pretty sure that this commit here works without unintended side effects, but I am not sure if ^std::__[0-9]*::__.* would regress debugability in unforeseen ways
There was a problem hiding this comment.
libc++ ABI namespace doesn't have to be just a number, it can be any string that starts with __. For example, in some of our Google projects we use __ktl and __pw; libFuzzer uses __Fuzzer in its internal libc++ build.
There was a problem hiding this comment.
right. I posted this comment before I saw your fix on main. By now, the commit contains `^std::__[^:]::
lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
Outdated
Show resolved
Hide resolved
| Arg<"Boolean">, Desc<"Show the extended backtrace, if available">; | ||
| def thread_backtrace_unfiltered : Option<"unfiltered", "u">, Group<1>, | ||
| Desc<"Filter out frames according to installed frame recognizers">; | ||
| Desc<"Do not filter out frames according to installed frame recognizers">; |
There was a problem hiding this comment.
Can just push this as an NFC separately
There was a problem hiding this comment.
I don't have direct push-access and piggy-backing it in this PR is less effort for me
| case Mangled::ePreferDemangledWithoutArguments: | ||
| return function_name_noargs; | ||
| } | ||
| }(); |
There was a problem hiding this comment.
We could just pass symctx.GetFunctionName(entry.mangling_preference) to the below is_contained right?
No need for the switch statement here and above and also no need to keep a map of m_used_manglings
There was a problem hiding this comment.
I wasn't sure about performance here. The code previously computed the demanded name only once and then checked against all frame recognizers. I am not sure how expensive it is to demangle a name
There was a problem hiding this comment.
The demangling gets chached anyway (in the Mangled class). So wouldn't worry about it
There was a problem hiding this comment.
By the way: I don't think I will be merging this part of my commit in the end. #105756 is very similar, and I think I will just rebase on it, after it got merged
There was a problem hiding this comment.
@vogelsgesang I closed my PR, feel free to copy&paste what you find useful.
There was a problem hiding this comment.
The demangling gets chached anyway (in the Mangled class). So wouldn't worry about it
ah, great! I didn't know this. I removed the additional caching from StackFrameRecognizer again
There was a problem hiding this comment.
@vogelsgesang I closed my PR, feel free to copy&paste what you find useful.
I did copy over:
- the improved comments in
StackFrameRecognizer.h - the printing improvements to
frame recognizer list(but I am using a slightly different format)
However, I did not change the VerboseTrapFrameRecognizer and AssertFrameRecognizer to use the mangled names. I will leave this for a follow-up commit.
c7cc7e9 to
32786ae
Compare
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
|
✅ With the latest revision this PR passed the Python code formatter. |
e21f87c to
cf62995
Compare
With this commit, we also hide the implementation details of `std::invoke`. To do so, the `LibCXXFrameRecognizer` got a couple more regular expressions. The regular expression passed into the `AddRecognizer` became problematic, as it was evaluated on the demangled name. Those names also included result types for C++ symbols. For `std::__invoke` the return type is a huge `decltype(...)`, making the regular expresison really hard to write. Instead, I added support for `AddRecognizer` to match on the demangled names without result type and argument types. By hiding the implementation details of `invoke`, also the back traces for `std::function` become even nicer, because `std::function` is using `__invoke` internally.
They are already cached in `Mangled`
cf62995 to
0ed2dc1
Compare
0ed2dc1 to
9f6630e
Compare
9f6630e to
e384eb9
Compare
Michael137
left a comment
There was a problem hiding this comment.
LGTM (with some minor comments). I'd wait for @adrian-prantl to also review this
lldb/source/Plugins/SystemRuntime/MacOSX/AbortWithPayloadFrameRecognizer.cpp
Outdated
Show resolved
Hide resolved
lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
Outdated
Show resolved
Hide resolved
lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py
Outdated
Show resolved
Hide resolved
lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py
Outdated
Show resolved
Hide resolved
| substrs=["recognizer.MyFrameRecognizer, module a.out, symbol bar"], | ||
| substrs=[ | ||
| "recognizer.MyFrameRecognizer, module a.out, demangled symbol bar" | ||
| ], |
There was a problem hiding this comment.
Don't seem to have tests for this with ePreferDemangledWithoutArguments
There was a problem hiding this comment.
Unfortunately, I was not able to write such a test case. Note that the frame recognizer add command does not yet expose a way to match on anything else than demangled names. I agree that we should be adding test cases here, as soon as frame recognizer add supports choosing the symbol mangling.
@adrian-prantl Are you planning to extend frame recognizer add accordingly?
There was a problem hiding this comment.
Unfortunately, I was not able to write such a test case. Note that the
frame recognizer addcommand does not yet expose a way to match on anything else than demangled names. I agree that we should be adding test cases here, as soon asframe recognizer addsupports choosing the symbol mangling.@adrian-prantl Are you planning to extend
frame recognizer addaccordingly?
It's not on my immediate todo list, but it would be an obvious next step.
|
@vogelsgesang Can we please merge this PR to unbreak the builders that were broken by #104523? |
655a8eb to
3763051
Compare
|
Looks like this is failing on the Darwin bot: https://green.lab.llvm.org/job/llvm.org/view/LLDB/job/as-lldb-cmake/10463/ |
|
fixed in #106281 |
With this commit, we also hide the implementation details of `std::invoke`. To do so, the `LibCXXFrameRecognizer` got a couple more regular expressions. The regular expression passed into `AddRecognizer` became problematic, as it was evaluated on the demangled name. Those names also included result types for C++ symbols. For `std::__invoke` the return type is a huge `decltype(...)`, making the regular expresison really hard to write. Instead, I added support to `AddRecognizer` for matching on the demangled names without result type and argument types. By hiding the implementation details of `invoke`, also the back traces for `std::function` become even nicer, because `std::function` is using `__invoke` internally. Co-authored-by: Adrian Prantl <aprantl@apple.com> (cherry picked from commit dd060bd)
With this commit, we also hide the implementation details of
std::invoke. To do so, theLibCXXFrameRecognizergot a couple more regular expressions.The regular expression passed into
AddRecognizerbecame problematic, as it was evaluated on the demangled name. Those names also included result types for C++ symbols. Forstd::__invokethe return type is a hugedecltype(...), making the regular expresison really hard to write.Instead, I added support to
AddRecognizerfor matching on the demangled names without result type and argument types.By hiding the implementation details of
invoke, also the back traces forstd::functionbecome even nicer, becausestd::functionis using__invokeinternally.Co-authored-by: Adrian Prantl aprantl@apple.com