Title: Improved __attribute__((cleanup)) Through defer H1: Improved__attribute__((cleanup))
Throughdefer
Shortname: 3199 Revision: 0 !Previous Revisions: None Status: P Date: 2023-12-10 Group: WG14 !Proposal Category: Change Request, Feature Request !Target: C2y/C3a Editor: JeanHeyd Meneide, phdofthehouse@gmail.com Editor: Shepherd (Shepherd's Oasis LLC), shepherd@soasis.org URL: https://thephd.dev/_vendor/future_cxx/papers/C%20-%20Improved%20__attribute__%28%28cleanup%29%29%20Through%20defer.html !Paper Source: GitHub Issue Tracking: GitHub https://github.com/ThePhD/future_cxx/issues Metadata Order: Previous Revisions, Editor, This Version, Paper Source, Implementation, Issue Tracking, Project, Audience, Proposal Category, Target Markup Shorthands: markdown yes Toggle Diffs: no Abstract: Many compilers's "cleanup" attribute has long-since provided scope-based, compile-time deterministic, well-known mechanism for the C language to clean up resources of all kinds (not just memory). This proposal attempts to standardize something as close to existing practice as possible while providing a select and measured few set of behaviors to ensure greater portability and usability in the C ecosystem.
path: resources/css/bikeshed-wording.html# Changelog # {#changelog} ## Revision 1 - February 4th, 2023 ## {#changelog-r1} - Include discussion of Linux kernel-based attempts at `defer` (using `__attribute__((cleanup(…)))`) (thanks, Miguel Ojeda!) - Previously, in the [[#cpp.compat]] section, this paper mentioned a vendor who supports C and C++ that has a demangler that outstrips their C implementation in engineering effort. The vendor in question clarified that a pure C17 compiler given the burden of attempting to mangle C++ would result in a significant engineering effort that could outstrip such, and that currently — for their implementation — that burden has already been surpassed. - Document that destructors need mangling. - Discuss the `__try` and `__finally` MSVC extensions in relation to this. - Improve the wording: - remove weasel-y words like "such as" and similar from the wording; - add `switch` back into the wording that was present in a revision for jumping into/over; - and, remove `switch` from the wording for exiting a block. ## Revision 0 - December 10th, 2023 ## {#changelog-r0} - Initial release. ✨ # Introduction, Motivation, and Prior Art # {#intro} > `defer` in C would have avoided probably half of the issues ever caught by the Clang Static Analyzer’s malloc/free and retain/release checking, at least ten years ago when I worked on it. > > — [Jordan Rose, Formerly Swift @ Apple, Currently Signal, December 1st, 2023](https://social.belkadan.com/@jrose/statuses/01HGKXQQAM9H4DSZ4S9FCV6P4C) The need to clean up resources, undo partially-successful function invocations, and perform actions upon early return has been a computing need since we started having computers that were capable of calculation. This need intensified with the introduction of resources that work with the boundaries of a system, from sockets and files to memory allocations and parallelism primitives. We have also had a large variety of failures related to `goto cleanup;` or `goto fail;`-style of programming. It becomes incredibly precarious to balance such code correctly, and sometimes individuals even opt out of `goto` entirely and simply repeat necessary cleanup on exit of each scope:
So this isn't as ... "nice" ... as those "`goto err1;` ... `goto err2;`" style solutions, this is my own little piece of hell :) It's the result of retrofitting freeing memory in error situations to an application which used to not care about that because it was a one-shot thing.As stated by the code author, this is not more robust code. In fact, this sort of idiom — while explicit — has the user repeating themselves multiple times over and over again. Each nested scope, each conditional, is a chance to potentially forget to free an element, or free too many elements too many times. Conversely, there is the opposite idiom where — in an attempt to reduce the sort of code as shown above — the author deploys a series of outside-in, inside-out `goto`s and labels to handle failure. Other times, there is only one failure and the use of sentinel values (such as `INVALID_HANDLE` or the null pointer constant or `0`) are used to effectively have no-op `free`/`DestroyHandle`/`ReleaseResource`/etc. calls. But, even a simple set of conditionals with only one `goto fail;` can be error-prone in conjunction with formatting failures and other issues. ## Language-based Solutions ## {#intro-language} Language-based solutions are far superior to library-based solutions for this problem. They provide a level of guarantees to make code a lot cleaner than normal. - [GCC/Clang attribute __attribute__((cleanup(…)))](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute), which takes a function with a single `void*` referencing the value passed in; - [GTK's glib g_autoptr](https://blogs.gnome.org/desrt/2015/01/30/g_autoptr/), which works only on Clang/GCC using the language-based `__attribute__((cleanup(…)))` functionality; - [qemu's Lockable](https://github.com/qemu/qemu/blob/master/include/qemu/lockable.h#L21), which uses the above `g_autoptr` to build a macro to do proper scoped locks; - [an ObjC defer](https://nshipster.com/new-years-2016/#swifts-defer-in-objective-c) based on `__attribute__((cleanup(…)))`; - [an Apple Open Source auto-cleanup](https://opensource.apple.com/source/Libc/Libc-1439.141.1/libdarwin/h/cleanup.h.auto.html), based still on `__attribute__((cleanup(…)))`; - and, [MSVC's try-finally blocks](https://learn.microsoft.com/en-us/cpp/c-language/try-finally-statement-c), which offer a more imperative way of doing things rather than the declaration-based attribute. Even individuals with legitimate technical grievances against C++ speak highly of things such as `g_autoptr`: > It takes about 10 minutes to get used to the “`g_autoptr(foo)`” stuff, and then its not a big deal. > > I’ve basically removed all of my “`goto error;`” handling. > > If you need MSVC support, clearly you shouldn’t be using this yet. My hope is that the clang interop with MSVC will help make that a non-issue before long. > > XlC might actually support it with recent versions. They are pretty good about tracking GCC frontend features. I doubt suncc will get to it though. > > — [Christian Hergert, February 5th, 2015](https://blogs.gnome.org/desrt/2015/01/30/g_autoptr/#comment-1301) This is not the first time programmers have moved towards this sort of solution. However, doing so often came at the cost of needing to leave the C language, or — as done above — embracing a potentially high degree of non-portability to achieve the goals. As a Standards Committee, it would seem prudent to produce a viable implementation of this that is capable of satisfying all of C's stakeholders without adding undue burden to the language. The only drawback of `__attribute__((cleanup(…)))` is that it is tied to passing in a single function, and only taking a single `void*` parameter. It becomes difficult to properly coordinate additional information to the function without figuring out potentially contested data transmission mechanisms between the function put into the `cleanup` portion of the attribute and the actual run of the function itself. Still, its benefits clearly outweigh its drawbacks, enough at least to warrant it being added to the Linux Kernel recently[[linux-cleanup]]. It has begun to see use for the typical cases that were outlined above: - automatic `free`ing of a pointer; - automatic lock/release of a `mutex` or similar multi-threading resource types; - and other resource management. This points to sign of the ecosystem at-large beginning to mature in a direction towards `defer`-style semantics (or constructor/destructor-style semantics). Therefore, we believe now is a prudent time to start moving this sort of functionality into the C standard. ## Library-based Solutions ## {#intro-library} Just because there are language solutions to the library that others have built on top of, it has not stopped others from creating their own solutions produced entirely within library-based code. There are both simple and robust examples of this sort of code in the wild, with some of the most readily available being: - [moonchilled's Defer/Deferrable macros](https://github.com/moon-chilled/Defer) (requires top-of-scope declarations); - and, [Jens Gustedt's guard and defer macros](https://gitlab.inria.fr/gustedt/defer) (an incredibly robust implementation using all sorts of tricks to provide the right behavior). The details of these libraries — both the simple version in moonchilled's rendition and the more robust version in Jens Gustedt's offering — are testaments to the ability of a dedicated individual to work through the conditions of their language to produce remarkable code to solve the engineering problems they are facing. Unfortunately, each has a has a wide variety of implementation drawbacks: - adds execution overhead to register each `Defer(…)` or similarly-named macro into the current scope; - generally requires a change in the way the program is written (e.g., using `defer_return` instead of `return` or `defer_break` instead of `break`); - and, typically has a maximum number of registrations (as the storage has be be pre-allocated at the start of the scope to hold deferral registrations). Even when the final code compiles down to something fairly efficient, these macros still tend to cost extra stack space or take up additional binary size through (potential) dynamic allocations to hold defers that go beyond a certain limit and need extra space to hold certain kinds of callbacks. This means that most code written using library solutions end up fairly suboptimal to the language based or preprocessor-based solutions. # Design # {#design} The design of this feature chooses 4 core tenets upon which it is based: - compile-time available (no run-time construction or space needed, do not pay for what you do not use); - binds to the innermost scope (closest); - can only appear as part of a statement (e.g., at block scope); - is **not** a full call stack-unwinding mechanism. These four tenets are important to cleave close to existing practice and avoid any potential run-time overhead. By making it a locally-scoped entity, we can have many more statements and much more data accessible to what is the effective equivalent of the `cleanup` function. This prevents us from fiddling with making larger structures and calling a function, when we can instead have it entirely in-line and improve code motion and optimization opportunities that would not be available otherwise. Notably, this is **not** similar to C++'s "RAII" (Resource Acquisition is Initialization) idiom in the specific area that `std::terminate()` will run every object's destructor up the entire call stack. While C++ implementations with extensions may offer to give the same behavior as a run destructor for `defer` blocks in their C++-with-extensions mode, C does not need to have any `terminate()`-like function or unwinding capabilities at all. We explain these tenets and our design choices in the following subsections. ## Defer Binding: Scope-Based ## {#design-binding} `defer` binds to the scope it was defined in. There is one other choice for `defer`, which is to bind to the scope of the function call. This is the programming language Go's choice of `defer`[[go-defer]]. Choosing function-based binding for C would be an unmitigated disaster of corner cases and add the potential for needing run-time accumulation (e.g., dynamic allocation) of resources for `defer` in order to handle `defer`s which appear in loops or other constructs whose execution is only determined at run-time but is still scoped to a compile-time entity like the scope of a function. To give an example of how quickly this behavior unravels itself, consider the following Go code: ```go for i := 0; i < 100000; i++ { mutex.Lock() defer mutex.Unlock() *counter += 1 } ``` This code immediately deadlocks. The fix to this in Go is to write the loop like this: ```go for i := 0; i < 100000; i++ { func() { mutex.Lock() defer mutex.Unlock() *counter += 1 }() } ``` This is just an extremely long-winded way of having a scope-based `defer`. It is not all bad for function-based `defer`: advantages include queueing up a piece of work to be done only if certain conditions are met. E.g., one can place a `defer` inside of an `if` statement and then having it run at the end of the function if and only if that `if` was entered. However, the cost of such behaviors means attempting to shoehorn a design from Go which has the backing of a garbage collector and on-demand allocation. The first go snippet above, if it had not deadlocked, required dynamic allocation in an earlier version of Go. It took significant optimization work to get to a place where this would no longer be the case. We know for a fact that many C compilers are averse to taking control of hidden dynamically-sized (not necessarily heap) allocations. It can often result in issues in the portability of code to smaller platforms. We also know for a fact that memory is neither free nor cheap in the C programming language; Go can pull this off because it has a run-time that can manage its garbage collector and is generally geared towards high-resource environments (even if it uses those resources efficiently). As a language feature, we cannot prioritize a design which may require an unbounded amount of code to be (potentially) stored in heap space so that it can be run as a callback, with potentially-saved data from each iteration of a loop stored in that construct as well. ## `defer` Syntax and Grammar: *secondary-block* ## {#design-syntax} The syntax of a `defer` block simply uses: *defer-statement*: :: **defer** *secondary-block* A *secondary-block* is the same grammar term used for e.g. `if` statements, so all of the typical syntactic constructs — even the ones that look questionable — are allowed. We expect coding guidelines and build-failing tools to apply conformance to make these more legible (e.g., clang-tidy) and resistant to the usual failure cases. We also chose the all-lowercase name `defer` for this feature. This is, technically, a breaking change. We do not mind swapping every instance of `defer` in this paper to be `_Defer`, or `_Cleanup`, or `_Meow`; the exactly spelling of the introductory keyword is of little consequence to us. We use `defer` in this paper to draw clear connections to the existing practice in other languages which use a similar keyword, such as the Go programming language. `defer`/`Defer` is also, as shown in the existing practice above, a very common spelling for this feature. ## Reference Captures "by Default" ## {#design-capture} This proposal does not tie the acceptance of the proposal to the presence of an explicit capturing clause, as was the case in other versions[[N2895]][[N2542]]. It simply allows for any variables that are visible in the scope of the `defer` statement to be allowed like any other named entity. No implicit or invisible copy of the variables is performed: it simply refers to those variables just as in the same way as the rest of the surrounding code. This is the safest and best way to handle the way that this feature works. The reason this is critically important is due to the potential to double-free. For example, consider the following function call: ```cpp void f () { void* p = malloc(meow); defer free(p); /* … */ if (some_important_condition) { take_ownership_and_use(p); p = NULL; } /* … */ } ``` If captures are done by-value (the pointer's value is copied and held onto for the duration of the code until the function is exited by some means), then this example is a potential double-free. This is an enormous footgun. Copying by-value also introduces a (hidden, semi-uncontrolled) state that will exist on all implementations until optimizers can potentially get rid of the extra copies stored in the `defer` statement. We note that this is the position Swift tends to take with its closures: values are referred to by-reference if they can prove such accesses are safe, but otherwise decay to by-value copies. However, Swift generally codifies and relies more on its ability to perform certain optimizations, whereas C implementations are allowed to be far weaker in terms of their optimization and translation/evaluation/execution guarantees. Copying by-value for `defer` is bad design in-general; anything that exists in the same scope and cannot escape said scope (such as statement expressions, `defer` statements, and otherwise) should always refer to existing variables through their name and at the same location/address. There is no risk of failure here because `defer` statements are not objects or declarations; they do not occupy a fixed amount of space as a reference-able object, they cannot be passed to `sizeof`, they cannot be copied or put on the heap versus the stack or some other form of storage location. They are simply a form of code (and code organization) like other flow control and code control entities. Treating them like callbacks (e.g., things that can be saved/transported/invoked at an arbitrary point later in time) is antithetical to the feature itself. For these reasons, `defer` should refer to existing variables as thought they were normal l-values (because they are and they should be). We anticipate that, in a future where Lambdas/Nested Functions/etc. are possible, specific styles of capture can be obtained through their use where it will be explicit and documented neatly by the use of such hypothetical features themselves. ## "Why Does This Not Unwind The Whole Call Stack??" ## {#design-unwinding} No C implementation provides a compiler-driven unwinding that we could find, even with `__attribute__((cleanup(…))) p = malloc(2);`. There is one notable exception, but it requires code to be in "C++ mode" (or have the equivalent of `-fexceptions` passed to the compiler to enable it in "C mode"). Right now, calling any of: - `exit`; - `_Exit`; - `quick_exit`; - `thrd_exit`; - or, `abort`; did not produce any code that called either the cleanup-annotated variables, or other code. `defer` works similarly: no stack unwinding or call stack back-travel is done when any function that refuses to return and returns control to the host environment is done. Note: This is compatible with C++ semantics for a similar C++ feature: constructors and destructors. It is noteworthy that not even C++ destructors run on the invocation of any of these functions, either. (You can test that assumption [here](https://godbolt.org/z/185z5E4aM).) They have to use the C++-specific function `std::terminate()` and work with the `std::terminate_handler` in order to get appropriate unwinding behavior. Therefore, there is no precedent — not even from C++ — that C or C++ code should appropriately and carefully unwind the stack. `defer …`, therefore, will not provide this functionality. This makes it cheaper and easier to implement for platforms that do not have `__attribute__((cleanup(…)))`, while also following existing practice to the letter. Notably, the "cheapness" and "ease" that will come from the implementation means that at no point will there ever need to be a maintained runtime of unwind scopes or exception handling-alike tables. In fact, no storage of any form of propagation information is necessary for this feature. It simply incentivizes the programming practices currently available to C programs: error codes, structured returns (with error codes embedded), and other testable function outputs in conjunction with better-defined cleanup code. The one place this does not hold up is `thrd_exit`. Consider the following code: ```cpp #includepath: assets/C - defer/mort96 code.bs— Martin Dørum, with code from: [Housecat](https://github.com/mortie/housecat/blob/6a1b76a8b41c5ad0fea2698f8559f171feb43c72/src/build/plugins.c)
## Modify 6.4.1 Keywords to include `defer` ## {#wording-6.4.1}5.1.2.2.3 Program terminationIf the return type of the `main` function is a type compatible with `int`, a return from the initial call to the main function is equivalent to calling the `exit` function with the value returned by the `main` function as its argument after all active `defer` statements of the function body of main have been executed; …
## Modify §6.8 Statements's *unlabeled-statement* grammar production to include a new *defer-statement* ## {#wording-6.8}6.4.1 KeywordsSyntax*keyword*: one of … **default** **defer** **do** …
## Add a new §6.8.7 's section describing the new *defer-statement* ## {#wording-6.8.7}6.8 StatementsSyntax*statement*: :: *labeled-statement* :: *unlabeled-statement* *unlabeled-statement*: :: *expression-statement* :: *attribute-specifier-sequence**opt* *primary-block* :: *attribute-specifier-sequence**opt* *jump-statement* :: *defer-statement* *primary-block*: :: *compound-statement* :: *selection-statement* :: *iteration-statement* *secondary-block*: :: *statement*
## OPTIONAL: Add to 6.8.7 Defer statements a new paragraph 3 additional constraint to reject multiply-nested `defer`. ## {#wording-optional.6.8.7}6.8.7 Defer statementsSyntax*defer-statement*: :: **defer** *secondary-block*DescriptionLet *D* be a defer statement, *S* be the secondary block of *D* referred to as its deferred content, and *E* be the enclosing block of *D*.ConstraintsJumps by means of `goto` or `switch` into *E* shall not jump over a defer statement in *E*.Jumps by means of `goto` or `switch` shall not jump into any defer statement.Jumps by means of `return`, `goto`, `break`, orW `continue` shall not exit *S*.SemanticsWhen execution reaches a defer statement *D*, its *S* is not immediately executed during sequential execution of the program. Instead, *S* is executed upon: - the termination of the block *E* (such as from reaching its end); - or, any exit from *E* through `return`, `goto`, `break`, or `continue`. The execution is done just before leaving the enclosing block *E*.Multiple defer statements execute in the reverse lexical order they appeared in *E*. Within a single defer statement *D*, if *D* contains one or more defer statements of its own, then these defer statements are also executed in reverse lexical order at the end of *S*, recursively, according to the rules of this clause.If *E* has any defer statements *D* that have been reached and their *S* have not yet executed, but the program is terminated or leaves *E* through any means including: - a function with the deprecated `_Noreturn` function specifier, or a function annotated with the `noreturn`/`_Noreturn` attribute, is called; - or, any signal `SIGABRT`, `SIGINT`, or `SIGTERM` occurs; then any such *S* are not run, unless otherwise specified by the implementationFN0✨). Any other *D* that have not been reached are not run.FN0✨)The execution of deferred statements upon non-local jumps or program termination is a technique sometimes known as "unwinding" or "stack unwinding", and some implementations perform it. See also ISO/IEC 14882 Programming languages — C++, section [except.ctor].If a non-local jump (e.g. `longjmp`) is used within *E* but before the execution of *D*: - if control leaves *E*, *D*'s statements will not be executed; - otherwise, if control returns to a point in *E* and causes *D* to be reached more than once, there is no effect.FN1✨)FN1✨)This is because the "execution" of a defer statement only lets the program know that *S* will be run. There is no observable side effect to repeat from reaching *D*, as the manifestation of any of the effects of *S* will happen when if and only if it is exited or terminated as previously specified.If a non-local jump (e.g. `longjmp`) is executed from *S* and control leaves *S*, the behavior is undefined.If a non-local jump (e.g. `longjmp`) is executed outside of any *D* and: - it jumps into any *S*; - or, it jumps over any *D*; the behavior is undefined.**EXAMPLE 1**: Defer statements cannot be jumped over or jumped out of. ```cpp #includeint f () { goto b; // constraint violation defer { printf(" meow"); } b: printf("cat says"); return 1; } int g () { return printf("cat says"); defer { printf(" meow"); } // okay: no constraint violation, not executed // print "cat says" to standard output } int h () { goto b; { // okay: no constraint violation defer { printf(" meow"); } } b: printf("cat says"); return 1; // prints "cat says" to standard output } int i () { { defer { printf("cat says"); } // okay: no constraint violation goto b; } b: printf(" meow"); return 1; // prints "cat says meow" to standard output } int j () { defer { goto b; // constraint violation printf(" meow"); } b: printf("cat says"); return 1; } int k () { defer { return 5; // constraint violation printf(" meow"); } printf("cat says"); return 1; } int j () { defer { b: printf(" meow"); } goto b; // constraint violation printf("cat says"); return 1; } int k () { goto b; // okay: no constraint violation { b: defer { printf("cat says"); } } printf(" meow"); return 1; // prints "cat says meow" to standard output } int m () { goto b; // constraint violation { defer { printf(" meow"); } b: } printf("cat says"); return 1; } ``` **EXAMPLE 2**: All the expressions and statements of an enclosing block are evaluated before executing defer statements. After all defer statements are executed, then the block is left. ```cpp int main () { int r = 4; int* p = &r; defer { *p = 5; } return *p; // return 4; } ```**EXAMPLE 3**: It is implementation-defined if defer statements will execute if the exiting / non-returning functions detailed previously are called. ```cpp #include#include int main () { void* p = malloc(1); if (p == NULL) { return 0; } defer free(p); exit(1); // "p" may be leaked } ``` **EXAMPLE 4**: Defer statements, when execution reaches them, are tied to their enclosing block. ```cpp #include#include int main () { { defer { printf(" meow"); } if (true) defer printf("cat"); printf(" says"); } // "cat says meow" is printed to standard output exit(0); } ``` **EXAMPLE 5**: Defer statements execute in reverse lexical order, and nested defer statements execute in reverse lexical order but at the end of the defer statement they were invoked within. The following program: ```cpp int main () { int r = 0; { defer { defer r *= 4; r *= 2; defer { r += 3; } } defer r += 1; } return r; // return 20; } ``` is equivalent to: ```cpp int main () { int r = 0; r += 1; r *= 2; r += 3; r *= 4; return r; // return 20; } ```**EXAMPLE 6**: Defer statements can be executed within a `switch`, but a `switch` cannot be used to jump over a defer statement. ```cpp #includeint main () { void* p = malloc(1); switch (1) { defer free(p); // constraint violation default: defer free(p); break; } return 2; } ```
Note: 📝 Editor: also edit **Example 4** and **Example 5** with `// constraint violation` in the appropriate places, and change the description to make it clear it is a constraint violation. ## Modify Annex J's list of undefined behaviors with non-local jump undefined behavior (e.g. `longjmp`) ## {#wording-annex.j} Note: 📝 For the editor to do within the Annex J undefined behavior list.6.8.7 Defer statementsSyntax*defer-statement*: :: **defer** *secondary-block*DescriptionLet *D* be a defer statement, *S* be the secondary block of *D*, and *E* be the enclosing block of *D*.ConstraintsA defer statement shall not appear within another defer statement.
{ "N2895": { "authors": [ "Jens Gustedt", "Robert C. Seacord" ], "title": "N2895: A simple defer feature for C", "href": "https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm", "date": "December 31st, 2021" }, "n2542": { "authors": [ "Aaron Ballman", "Alex Gilding", "Jens Gustedt", "Tom Scogland", "Robert C. Seacord", "Martin Uecker", "Freek Wiedijk" ], "title": "Defer Mechanism for C", "publisher": "ISO/IEC JTC1 SC22 WG14 — Programming Languages, C", "href": "https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2542.pdf", "date": "August 8th, 2020" }, "go-defer": { "authors": [ "Andrew Gerrand" ], "title": "", "publisher": "The Go Project", "href": "https://go.dev/blog/defer-panic-and-recover", "date": "August 4th, 2010" }, "goto-fail": { "authors": [ "Arie van Deursen" ], "title": "Learning from Apple's #gotofail Security Bug", "publisher": "Arie van Deursen", "href": "https://avandeursen.com/2014/02/22/gotofail-security/", "date": "February 22nd, 2014" }, "cve-2021-3744": { "authors": [ "National Institute of Standards and Technology" ], "title": "CVE-2021-3744 Detail", "publisher": "National Vulnerability Database: NIST", "href": "https://nvd.nist.gov/vuln/detail/CVE-2021-3744", "date": "February 12th, 2023" }, "gnu-tls-bug-analysis": { "authors": [ "Sean Cassidy" ], "title": "The Story of the GnuTLS Bug", "publisher": "Sean Cassidy: Blog", "href": "https://www.seancassidy.me/the-story-of-the-gnutls-bug.html", "date": "March 14th, 2014" }, "p0052": { "authors": [ "Peter Sommerlad", "Andrew L. Sandoval", "Eric Niebler", "Daniel Krügler" ], "title": "Generic Scope Guard and RAII Wrapper for the Standard Library", "publisher": "ISO/IEC JTC1 SC22 WG21 — Programming Languages, C++", "href": "https://wg21.link/p0052", "date": "February 19th, 2019" }, "linux-cleanup": { "authors": [ "Peter Zijlstra" ], "title": "locking: Introduce __cleanup() based infrastructure", "publisher": "infradead.org, kernel.org", "href": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/cleanup.h", "date": "June 26th, 2023" } }