{"id":105870,"date":"2021-11-03T07:00:00","date_gmt":"2021-11-03T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105870"},"modified":"2021-11-03T10:12:16","modified_gmt":"2021-11-03T17:12:16","slug":"20211103-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211103-00\/?p=105870","title":{"rendered":"A capturing lambda can be a coroutine, but you have to save your captures while you still can"},"content":{"rendered":"<p>We saw some time ago that <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190116-00\/?p=100715\"> capturing lambdas which are coroutines result in lifetime issues<\/a> because the lambda itself returns at the first suspension point, at which point there&#8217;s a good chance it will be destructed. After that point, any attempt by the lambda body to access those captured variables is a use-after-free bug.<\/p>\n<pre>winrt::IAsyncAction DoSomethingInBackgroundAsync()\r\n{\r\n    auto a = something();\r\n    auto b = something();\r\n    auto c = something();\r\n\r\n    auto callback = [a, b, c]()\r\n        -&gt; winrt::IAsyncAction\r\n        {\r\n            co_await winrt::resume_background();\r\n            DoSomething(a, b, c); \/\/ use-after-free bug!\r\n        };\r\n    return callback();\r\n}\r\n<\/pre>\n<p>This problem is so insidious that there&#8217;s a C++ Core Guideline about it: <a href=\"https:\/\/isocpp.github.io\/CppCoreGuidelines\/CppCoreGuidelines#Rcoro-capture\"> CP.51: Do not use capturing lambdas that are coroutines<\/a>.<\/p>\n<p>One workaround is to pass the captures as explicit parameters:<\/p>\n<pre>winrt::IAsyncAction DoSomethingInBackgroundAsync()\r\n{\r\n    auto a = something();\r\n    auto b = something();\r\n    auto c = something();\r\n\r\n    auto callback = <span style=\"color: blue;\">[](auto a, auto b, auto c)<\/span>\r\n        -&gt; winrt::IAsyncAction\r\n        {\r\n            co_await winrt::resume_background();\r\n            DoSomething(a, b, c); \/\/ use-after-free bug!\r\n        };\r\n    return callback(<span style=\"color: blue;\">a, b, c<\/span>);\r\n}\r\n<\/pre>\n<p>However, this workaround isn&#8217;t always available because you may not control the code that invokes the lambda.<\/p>\n<pre>void RegisterClickHandler(Button const&amp; button, int key)\r\n{\r\n    button.Click([key](auto sender, auto args)\r\n        -&gt; winrt::fire_and_forget\r\n        {\r\n            co_await winrt::resume_background();\r\n            NotifyClick(key);\r\n        });\r\n}\r\n<\/pre>\n<p>You aren&#8217;t the one who invokes the lambda. That lambda is invoked by the Click event, and it passes two parameters (the sender and the event arugments); there&#8217;s no way to convince it to pass a <code>key<\/code> too.<\/p>\n<p>One idea would be to extract the work into a nested lambda. We control the invoke of the nested lambda and can pass the extra parameter that way.<\/p>\n<pre>void RegisterClickHandler(Button const&amp; button, int key)\r\n{\r\n    button.Click([key](auto sender, auto args)\r\n        -&gt; winrt::fire_and_forget\r\n        {\r\n            <span style=\"color: blue;\">return [](auto sender, auto args, int key)<\/span>\r\n            -&gt; winrt::fire_and_forget\r\n            {\r\n                co_await winrt::resume_background();\r\n                NotifyClick(key);\r\n            }<span style=\"color: blue;\">(std::move(sender), std::move(args), key)<\/span>;\r\n        });\r\n}\r\n<\/pre>\n<p>The outer lambda is not a coroutine. It&#8217;s just calling another lambda and propagating the return value.<\/p>\n<p>The inner lambda is a coroutine. To be safe from use-after-free, it is a captureless coroutine, and all of its state is passed as explicit parameters. Here is where we sneak in the extra <code>key<\/code> parameter.<\/p>\n<p>Now, I&#8217;m working a bit too hard here, because the coroutine body doesn&#8217;t use <code>sender<\/code> or <code>args<\/code> so I can accept them by universal reference (to avoid a copy) and just ignore them. To make sure I don&#8217;t use them by mistake, I&#8217;ll leave the parameters anonymous.<\/p>\n<pre>void RegisterClickHandler(Button const&amp; button, int key)\r\n{\r\n    button.Click([key](<span style=\"color: blue;\">auto&amp;&amp;, auto&amp;&amp;<\/span>)\r\n        -&gt; winrt::fire_and_forget\r\n        {\r\n            return [](<span style=\"color: blue;\">int key<\/span>)\r\n            -&gt; winrt::fire_and_forget\r\n            {\r\n                co_await winrt::resume_background();\r\n                NotifyClick(key);\r\n            }<span style=\"color: blue;\">(key)<\/span>;\r\n        });\r\n}\r\n<\/pre>\n<p>But what if I told you there was an easier way, where you can have your capturing lambda be a coroutine?<\/p>\n<p>The trick is to make copies of your captures into the coroutine frame before the coroutine reaches its first suspension point. (Note that this trick requires eager-started coroutines. Lazy-started coroutines suspend immediately upon creation, so you have no opportunity to copy the captures into the frame.)<\/p>\n<pre>void RegisterClickHandler(Button const&amp; button, int key)\r\n{\r\n    button.Click([key](auto&amp;&amp;, auto&amp;&amp;)\r\n        -&gt; winrt::fire_and_forget\r\n        {\r\n            <span style=\"color: blue;\">auto copiedKey = key;<\/span>\r\n            co_await winrt::resume_background();\r\n            NotifyClick(<span style=\"color: blue;\">copiedKey<\/span>);\r\n        });\r\n}\r\n<\/pre>\n<p>We explicitly copy the captured variable into the frame. When execution reaches the first suspension point at the <code>co_await<\/code>, the captured variables disappear. Lesser coroutine lambdas would tremble in fear, but not us! We laugh at the C++ language and say, &#8220;Go ahead, take those captured variables away and turn them into poison. It doesn&#8217;t matter because I made my own copy before you turned them evil.&#8221;<\/p>\n<p>The tricky part, though, is making sure that we don&#8217;t touch the original already-freed captures and operate only on our local copies. Somebody coming in later and making a change to the function may not realize that the captures are poisoned and try to use them. Oops. Look who&#8217;s laughing now.<\/p>\n<p>Next time we&#8217;ll look at a way to make this slightly less error-prone.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Save it all before it disappears.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-105870","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Save it all before it disappears.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105870","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=105870"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105870\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=105870"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105870"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105870"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}