defer reference implementation for C

Table of Contents

1 Description

This is a reference implementation of a C language mechanism and library-based implementation for error handling and deferred cleanup adapted from similar features in the Go Programming Language. This mechanism improves the

proximity – visibility – maintainability – robustness – security

of cleanup and error handling over existing language features.

The intent is to integrate the basic tools that are provided here into the C standard by providing a new library clause for a new header with the proposed name <stddefer.h>. A description of the general approach can be found in the following document:

1.1 An example for static cleanup handling

In the following code snippet we have one guarded block and three deferred statements.

guard {
  void * const p = malloc(25);
  if (!p) break;
  defer free(p);

  void * const q = malloc(25);
  if (!q) break;
  defer free(q);

  if (mtx_lock(&mut)==thrd_error) break;
  defer mtx_unlock(&mut);

  // all resources acquired
}

The idea is that we indicate with a defer keyword that the statement, e.g a call to free, is only to be executed at the end of the guarded block, and, that we want this action to happen unconditionally in which way ever the guarded block is left. For the three deferred statements together it says that they should be executed in the inverse order than they were encountered. So the control flow for this code example can be visualized as follows:

defer-image.png

Here, the dashed lines represent circumstancial control flow that might arise when some resources are not available or when the execution is interrupted by a signal.

This new technique has at least two advantages to commonly used C or C++ techniques:

proximity
The cleanup code (free or mtx_unlock) is coded close to the place where its need arises.
visibility
The cleanup code is not hidden in some previously defined function (such as for atexit handlers) or constructor/destructor pairs (C++)

For normal control flow (without intermediate return, exit, …) code with similar properties can be coded with existing tools. The above is equivalent to something like the following

{
   void * const p = malloc(25);
   if (!p) goto DEFER0;
   if (false) {
     DEFER1:
       free(p);
       goto DEFER0;
   }

   void * const q = malloc(25);
   if (!q) goto DEFER1;

   if (false) {
     DEFER2:
       free(q);
       goto DEFER1;
   }

   if (mtx_lock(&mut)==thrd_error) goto DEFER2;

   if (false) {
     DEFER3:
       mtx_unlock(&mut);
       goto DEFER2;
   }

   // all resources acquired

   goto DEFER3;
   DEFER0:;
}

Here, the if(false) clauses guarantee that the deferred statements are jumped over when they are first met, and the labels and goto statements implement the hops from back to front to execute the deferred statements eventually.

Obviously, most C programmers would not code like this but they would prefer to write down a linearization of the above, which is a quite common idiom for cleanup handling in C:

{
   void * const p = malloc(25);
   if (!p) goto DEFER0;

   void*const q = malloc(25);
   if (!q) goto DEFER1;

   if (mtx_lock(&mut)==thrd_error) goto DEFER2;

   // all resources acquired

   mtx_unlock(&mut);

 DEFER2:
   free(q);
 DEFER1:
   free(p);
 DEFER0:;
}

This has the advantage of only making the circumstantial control flow explicit (with three *=goto=) but it does that for the price of proximity; the cleanup code is far from the place where its need arises.

Nevertheless, even this linearization needs some form of naming convention for the labels. For more complicated code the maintenance of these jumps can be tricky and prone to errors. This shows another advantage of the defer approach:

maintainability
The cleanup specification is not dependent on arbitrary naming such as labels (C) or RAII classes (C++) and does not change when defer or break statements are added or removed.

Another important property that is much more difficult to implement in C (and that needs try/catch blocks in C++) is that all exits from the guarded block are detected and acted upon: break, return, thrd_exit, exit, panic, or an interruption by a signal. That is, unless there are nasal deamons flying around, we have a forth important property

robustness
Any deferred statement is guaranteed to be executed eventually.

This is different from C++'s handling of destructors, which are only guaranteed to be executed if there is a try/catch block underneath.

This principle of deferred execution extends to nested guarded blocks in a natural way, even if they are stacked in different function calls.

1.2 Dynamic versus static control flow for deferred statements

In the example above the control flow that was the result of deferring statements was static, that is the defer statements themselves were not placed into conditionals or loops. Being able to place them inside an if, for example, gives the programmer more flexibility, in particular it allows to only defer a statement if the corresponding resource has really been needed.

// create a small local buffer as a compound literal
double* p = (len > MAXLEN) ? 0 : (double[MAXLEN]){ 0 };

// if len is too big, go to the heap, instead
if (p) {
   p = defer_calloc(len, sizeof double);
   defer free(p);
}
// now use p as a buffer until we leave the function

Here, the deferred statement will only be evaluated at the termination of the guarded block only in cases where the buffer referenced by p is dynamically allocated. To support such constructs, our implementation determines the order and number of times each deferred statement is executed at run-time. This approach requires allocating storage at run-time to maintain defer state. The defer mechanism provides fault tolerance by continuing to operate properly in the event of the failure of a defer statement by enduring the deferred statement is executed to release the resource.

defer_calloc and the defer statement may both exhaust memory. If the call succeeds (so a resource is allocated) but the defer mechanism fails, the call to free is evaluated immediately and the execution of the enclosing guarded block is terminated by a panic with an error code of DEFER_ENOMEM.

1.3 Guarded blocks for function bodies

The current idea in our proposal to the C standardization is to have each function body to implement a guarded block, such that an explicit guard statement is not necessary for many common use cases. Generally, this can not be done by macro wonders but needs compiler magic.

The reference implementation achieves that for the gcc family of compilers by some internal magic by keeping track of the stack pointer. This has some caveats, see below. Nevertheless in this documentation we will always speak about a guarded block, regardless if a guard statement is given explicitly or if the function body acts as such.

1.4 Triggering the execution of deferred statements

There are three "severity" levels for termination of a guarded block, break or similar just terminates the guarded block, return terminates all guarded blocks of the same function and exit or panic of the same thread across all function calls.

A deferred statement can be triggered in two ways.

  • explicitly: by leaving a guarded block when coming to the end of it or by using break, defer_break, exit … from within.
  • implicitly: from a signal handler. Two signal handlers are provided defer_sig_flag and defer_sig_jump. Their actions are as their names indicate. The first just sets a flag and the user code then typically investigates that flag and acts accordingly. This would typically be used for a signal like SIGINT where the user interrupts the program execution. The second handler jumps to the first defer clause on the defer stack and triggers a panic. This one is more appropriate for signals that are related to faulty operations that by themselves are not recoverable.

    This implementation does not install any of these handlers by default.

1.5 Execution of deferred statements and panic recovery

Once execution starts inside a deferred statement, the condition that lead there can be investigated by means of recover. This returns an integer error code that indicates what happened. If it is 0, this execution of the defer clause is just "normal" retreat as of break, return or exit. If it is any other value something "remarkable" might have happened, generally a panic is on its way down in the call stack.

Once an error condition has been recovered, the responsibility to handle the error is passed back to the user. If they don't want that they may re-issue the panic by calling `panic(code, 0)`, where the 0 indicates that the same final action (e.g exit) as before the recovery is used.

1.6 Improved usage of certain C library features

As examples how this could be used by the C library, there are also augmented versions of storage allocation function, such as defer_malloc. The idea that these will panic if they encounter an out-of-memory condition. That has several effects

  • Explicit handling of such error conditions has not to be repeated at every call site.
  • Cleanup actions that the user has installed by means of defer clause will be called, e.g to close files or to free large allocations.
  • User code may establish a recovery mechanism through recover or alike. This will probably be rarely used by "normal" applications, but security critical applications would get a handle to avoid catastrophes, here.

1.6.1 Advantages of defer for error handling

Inspection of the assembler that is created for these functions shows that all of this generates very little overhead for the fast execution path. In general, this technique might provide a sensible way to provide the same error detection facilities as Annex K, but in a way that

  • preserves the prototypes of the functions
  • is thread safe.

1.7 Provided interfaces

The important interfaces of this tool are:

  • guard prefixes a guarded block
  • defer prefixes a defer clause
  • break ends a guarded block and executes all its defer clauses
  • return unwinds all guarded blocks of the current function and returns to the caller
  • exit unwinds all defer clauses of all active function calls of the thread and exits normally
  • panic starts global unwinding of all guarded blocks
  • recover inside a defer clause stops a panic and provides an error code

The features exit, quick_exit and thrd_exit are overloaded to do the unwinding as soon as this header is included. To be consistent when such code is linked to legacy object files that have been compiled differently, you should wrap the system calls. This can usually be done by providing `-wrap=exit` etc arguments to the linker. (You'd have to check your local manual for that.) This feature may (but shouldn't) be switched off by providing the environment variable DEFER_NO_WRAP to the build.

In a similar way, this implementation also offers to wrap the allocation functions of the C library, but the default here is the other way around: per default malloc and Co are not wrapped. You may enable this by setting DEFER_MALLOC_WRAP. Otherwise, you may use functions defer_malloc and similar to catch allocation errors.

1.8 Error codes

send with panic) are always positive, and that "system" error codes are generally negated errno numbers. To deal with such error codes in a portable way, for all POSIX error codes we provide an equivalent prefixed with DEFER_. E.g there is DEFER_ENOMEM that is the same as ENOMEM on platforms where that exists and some other unused number otherwise.

For signal numbers there is a sophisticated encoding in even smaller negative values. Because the system signal numbers may conflict with errno numbers we cannot use these directly. Instead, for all POSIX signal numbers there is constant that replaces the SIG prefix by a DEFER_ prefix. E.g there is DEFER_HUP to represent the signal SIGHUP regardless if that signal number actually exists on the platform.

Two convenience macros ease the use of this feature, defer_if as a pseudo selection statement, and recover_signal to recover just signals and do nothing if there was none.

1.9 Interoperability with C++

C++ has similar features for out-of-order code execution, namely destructors and catch clauses. The defer statement combines those two features into one.

This implementation provides features to translate between these mechanisms such that stack unwinding can be reliably performed in programs that mix C and C++. In particular, if called from C++ code, the C feature itself translates panics and requests for exit at the boundary into C++ exceptions. The "only" thing that C++ applications have to do for this to work is to establish a mark, that effectively the current language is C++. When the C++ code includes <stddefer_codes.h> this is done (by some hackery) for any translation unit that contains the function main.

Nevertheless, to really ensure that resources are released by such a C++ programm it is a good idea to have all exceptions caught. This will ensure that all destructors down to and including in main are called when unwound. An easy way to achieve this is to establish main as a try-catch block:

int main(int argc, char* argv[]) try {
  ... do your stuff ...
} catch (...) {
  throw;
}

The same could be done for functions that are called with threads of their own.

To establish interoperability for C++ code that might be called from C a bit more is needed. A wrapper testing_CPP for a function testing can be established as in this example:

#include "stddefer_codes.h"
#include "stddefer_cpp.h++"

int testing_CPP(int rec) {
  std::defer_boundary boundary; // Must be the first declared variable
  try {
    return testing(rec);
  }
  catch (...) {
    boundary.panic();
  }
  return 0;
}

That is, we need

  • a local variable of type std::defer_boundary for which the constructor memorizes the state prior to the call,
  • a try/catch clause that guarantees that exceptions are caught and that all the destructors are called when unwound
  • a call to the method panic() that either propagates any exception that was caught (if the caller is also C++) or that translates an exception to a panic (if the caller is C).

For the moment there is no automatization of such a wrapper feature.

When used consistently like that, arbitray back and forth translation between C defer and C++ exceptions is performed but has its limits in the expressiveness of the defer error codes. We translate some of the standard codes and exceptions:

error code (C or POSIX) exception (C++) remark
0 std::defer_exception(0) unwinding and exit or thrd_exit
-DEFER_EBADF std::ios_base::failure  
-DEFER_EDOM std::domain_error  
-DEFER_EINVAL std::invalid_argument  
-DEFER_ENOMEM std::bad_alloc  
-DEFER_EOVERFLOW std::overflow_error  
-DEFER_ERANGE std::out_of_range  
-DEFER_EUNDERFLOW std::underflow_error  
other std::defer_exception(other)  

Support for other codes might come in the future.

1.10 Implementation specifics

This implementation uses setjmp/longjmp as a main feature to jump to deferred statements within the same function or to unwind the call stack. We distinguishes "jumps" that are known to target the same function (_Defer_shrtjmp) and those that are known to jump to another function on the call stack (_Defer_longjmp). The unwind for a return will usually be all short jumps, whereas exit and other unwinding of the call stack always initiates a long jump.

This implementation is quite hackerish because it uses nested for loops to implement the principal macros defer and guard. It does so to ensure a mostly library only iplementation through macros, that could in principle work with any C compiler. This technique leads to code that is difficult to read and can therefore not be recommended.

Also, we use a intermediate processor for the production of the C (and C++) code called shnell. This uses #pragma annotations for the definition of complicated macros and for "code unrolling". So if you'd want to modify this implementation, you'd have to download shnell and modify the "real" sources.

1.10.1 Extension: using computed goto

The implementation can distinguish cases where basically all jumps are finally implemented as being long, or platforms where some shortcut for a short jump can be taken. Currently this is only implemented for gcc and friends that implement so-called "computed goto", that is labels for which addresses can be taken, and where these addresses then can be used in an extended goto feature. Such a specialized implementation can gain a lot on the unwind side (these then are mostly simple jumps), but they still have to keep track of all the guarded blocks and defer clauses with setjmp because these could be jumped to from other functions or from signal handlers.

1.10.2 Extension: Captured values for defer

Local variables that are used inside a deferred statement must be alive when the deferred statement is executed, so generally at the end of the guarded block. This policy is necessary to be able to react to changes to the variable during processing. For example, a pointer value that is to be freed could be rewritten by calls to realloc to resize the object.

If C will be extended with lamdas (hopefully in a nearer future) the defer feature

defer {
  ... code comes here ...
}

could be rewritten to default to something like

defer [&](){
  ... code comes here ...
}

that is we could say that defer receives a lambda with no parameters and executes it when leaving the guard. And the default for captures would then just be &, that is all variables are captured "by reference".

In some cases it makes sense for defer to capture a local variable by value. In lambda notation this would be someting like

defer [&, toto](){
  ... code comes here ...
}

where the variable toto would be frozen to the value it has at the point of the defer statement, and that particular value of toto would then be provided when the deferred statement is executed at the end of the guard. In the reference implementation the same can be achieved by using the alternative macro defer_capture

defer_capture(toto){
  ... code comes here ...
}

1.10.3 Omission of guard for function body

For gcc and friends the implementation is able to detect the boundaries between different function calls. For a defer that is not placed inside a guard this is important such that we do not accidentally attach it to a guard in another function. This only works with compiler magic that keeps track of the "stack pointer".

1.10.4 Synchronization of local variables

The guarantees for the state of local variables that setjmp/longjmp provides are weaker than those needed in our context. Therefore we use some other synchronization features that guarantee up-to-date values, namely atomic variables and fences. If these are not available, this implementation will still work, but precautions as described for defer have to be made.

1.10.5 Caveats

This implementation is special, because it is almost a "header only" implementation that overloads return (plus some others) with a macro. This is not ideal and can have some performance issues or even compilation failures with system-provided inline functions. As a rule of thumb, try to include the <stddefer.h> header as late as possible, such that there are as few interactions as possible.

2 Reference manual

2.1 *=defer=: ensure the execution of the deferred statement at the end of the guarded block

defer statement

Deferred statements may not themselves contain guarded blocks or other defer clauses, and shall not call functions that may result in a termination of the the execution other than panic. Additionally, such a call to panic must only occur after the current panic state had been tested with recover.

The deferred statement may use any local variable that is visible where the defer is placed and that is still alive when the deferred statement is executed, that is at the end of the surrounding guard or function body. This property is checkable at compile time, and a violation usually results in an abortion of the compilation. This implementation here puts everything in place, such that a deferred statement uses the last-written value for all variables, but the success of that depends on the presence of some synchronization feautures.

If such synchronization features are not available, local variables that may change between the defer itself and the execution of the deferred statement and that are used in the deferred statement must be declared volatile such that the latest value is taken into account. So as a rule of thumb, variables that you use inside a deferred statement should be qualified: const qualified for those where the defer clause should use the original value, and volatile qualified for those that should be used with their latest changes.

The implementation uses allocation as of calloc and free to maintain the list of defer clauses. In case that calloc fails, the defer clause is executed and then the whole execution is unwound by means of panic and an error argument of -DEFER_ENOMEM.

2.1.1 defer_capture: capture some variables and defer the execution of the depending statement

defer_capture([id0 [, id1 [, id2 ...]]]) statement

The argument list must be empty, or contain a list of variables. The effect is that those variables are evaluated, and the values are stored in a secret place. When the deferred statement is finally executed, address-less local variables with the same name and type (but const-qualified) are placed before the deferred statement, and are initialized with these frozen values.

2.2 *=guard=: mark a whole block as "guarded" and as using the defer mechanism.

guard compound-statement

If such a block is terminated normally or with a break or continue statement, all deferred statements that have been registered a defer statement are executed in reverse order of their registration. There is also a macro defer_break that can be used in contexts where break or continue statements would refer to an inner loop or switch statement. Also, return, exit, quick_exit and thrd_exit all trigger the execution of deferred statements, up to their respective levels of nestedness of guarded blocks.

Other standard means of non-linear control flow out of or into the block (goto, longjmp, _Exit, abort), do not invoke that mechanism and may result in memory leaks or other damage when used within such a guarded block.

For some of these constructs, there are replacements defer_goto, defer_abort etc that can be used instead.

2.3 *=panic=: Unwind the whole call stack and execute the deferred statements on the way down

noreturn void panic(int C);
noreturn void panic(int C, void F(int));

After all deferred statement of the current thread are executed in reverse order in which the defer statements have been encountered, in the last stack frame that has a defer or guard statement, F(C) is executed. Here F is a defer_handler and C is an error code. If omitted, F defaults to an implementation specific defer_handler.

Valid defer handlers are all functions that have the correct signature and that terminate the current thread or the whole execution. Prominent candidates from the C library are exit and thrd_exit.

This unwind chain can be stopped with a recover call within one of the executed deferred statements (or if a defer_if clause is used).

The error code should be negative if this is supposed to map to a system defined error condition as is for errno or a caught signal. Otherwise, all user supplied error numbers should be positive. When unwinding an error condition triggered by the system, the error code will always be negative.

2.3.1 defer_assert: assert a runtime condition or panic

void defer_assert(condition, code, string-literal);

This is similar to a usual call to the assert macro in that it asserts that condition holds. It cannot be switched off at compile time but the panic that is triggered can be recovered. In particular, the compiler can detect that the following code can only be reached if the condition holds, and optimize the code accordingly.

For example, the following asserts that pointer p is non-null, and otherwise triggers a ENOMEM panic.

defer_assert(p, -DEFER_ENOMEM, "unexpected allocation failure");
// p is now assumed to be non-null

2.4 Termination functions

noreturn void defer_exit(int);
noreturn void defer_thrd_exit(int);
noreturn void defer_quick_exit(int);
noreturn void defer__Exit(int);

The functionality of these functions is the same as their un-prefixed C library counterparts, only that instead of terminating the thread or the execution immediately they will unwind the stack of collected deferred statements.

Code that is compiled with the <stddefer.h> header will also just replace calls to the C library functions

noreturn void exit(int);
noreturn void thrd_exit(int);
noreturn void quick_exit(int);

by calls to these wrappers.

Other code that is not compiled with it, may still call the C library functions directly without unwinding. So when you are mixing such units of different origin you should use linker tricks such as -wrap-function=exit to help you replace these calls by the wrapped ones.

2.4.1 DEFER_NO_WRAP: force the usage of the unwrapped termination functions

Code compiled with this macro set, will not replace all usage of the C library termination functions by the wrapped versions as presented above. The use of this macro is not recommended.

2.5 *=recover=: stop a panic and/or return such the error code

int recover(void);

A call to this function must reside within a deferred statement.

This will only stop unwinding of the panic if the error code had not been 0. Normal break, return, thrd_exit, exit etc will not be be stopped and the guarded block, function, thread or program will finish when all the associated deferred statements have been executed.

If the error code is non-zero, execution of the defer clause then continues as if the nearest guarded block had been broken by break. That is, the execution of the defer clauses of that particular guarded block are continued and then execution resumes at the end of that guarded block.

In general, error codes provided by the system will be negative and user provided error codes shall be positive. So to see if a system error occurred you may e.g compare the value to -ERANGE, or, if the error originated from a caught signal to DEFER_SIGNO(SIGTERM).

2.5.1 defer_if

defer_if(err)
    statement-1
else
    statement-2

would be equivalent to a defer clause

defer {
    register int const err = recover();
    if (err)
        statement-1
    else
        statement-2
}

This stops an unwind originating from a panic call or from a signal with non-zero error code (or similar), returns the error code in a local int variable named err, and executes the depending statement if the value is non-zero, or an alternative else clause, if any, if the value is zero. The else clause is optional.

the variable err can then be used inside the if or else clauses, but it is not mutable and its address cannot be taken. And it is a bit useless within the else clause, because we know that it is zero, there.

2.5.2 defer_show: show information about the latest panic on stderr

void defer_show(int a);

This can be used as diagnosis tool after a recover to provide feedback to the user what went wrong with the execution.

2.5.3 recover_signal: stop an unwind originating from a signal and/or return the signal number

int recover_signal(void);

This differs from recover because it only reacts on signals that are caught by the provided signal handlers (defer_sig_flag and defer_sig_jump), not on other break or panic events. It can be used in any context and does not need a surrounding guarded block or defer clause. Its cost is the lookup of a thread local variable and a memory synchronization with that variable. So it should probably not be placed inside performance critical loops.

When a defer signal occurs within a guarded block or defer clause and is not caught by means of recover_signal, the execution is considered to be compromised, a panic results, and the deferred statements of the thread are unwound.

This returns the number of the signal that has been caught.

A typical use of this in conjunction with defer_sig_flag would be an active loop:

int main(void) {
  // Establish the signal handler for user interrupts.
  signal(SIGINT, defer_sig_flag);

  while (active) {
      if (recover_signal()) {
          puts("caught signal, stopping loop and continuing");
          break;
      }
      ... do whatever you have to do ...
  }
}

Here, an INT event can occur at any point of the computation before or inside the while loop. At the first detection of a signal, the while loop then is broken, and execution continues after it. This guarantees that the whole loop body is always executed until its end.

2.6 Signal handlers

The provided signal handlers use extensions that might not be available everywhere. In particular, they use the fact that thread-local variables can be accessed from a signal handler.

2.6.1 defer_sig_flag: a signal handler that flags the execution of a thread.

void defer_sig_flag(int sig);

This one is not too intrusive and hands control for the signal back to the user code. Thus handling is delayed until the application code reaches the end of a guarded block and/or actively issues a recover_signal operation to receive the code.

Generally this might be used to handle signals triggered by the user via the terminal.

This is not suited for signals that jump back to exactly the same code location where some repair work has to be performed to continue, such as invalid arithmetic or segmentation faults.

2.6.2 defer_sig_jump: a signal handler that flags the execution of a thread and jumps to the first defer clause

void defer_sig_jump(int sig);

This is intrusive and jumps back into the next available defer clause. Therefore this has to assume that the error is caught by the same thread that is to perform that defer clause, and not all platforms may guarantee this.

This is suited for signals that otherwise would jump back to exactly the same code location where some repair work has to be performed to continue, such as arithmetic or segmentation faults.

Probably the C standard function signal itself is not appropriate for this kind of handler, because signals should be switched off during the handling of such interrupts. Your platform might propose a better tool than signal for that. Be careful.

2.7 Encapsulated storage management functions

void* defer_malloc(size_t size);
void* defer_calloc(size_t nmemb, size_t size);
void* defer_realloc(void *ptr, size_t size);
void* defer_aligned_alloc(size_t alignment, size_t size);

The functionality of these functions is the same as their un-prefixed C library counterparts, only that instead of returning a null pointer on failure they will issue a panic. For example, defer_malloc, will execute panic(-DEFER_EINVAL) when called with a size of 0 and execute panic(-DEFER_ENOMEM) when the allocation fails.

This has two advantages. First, when using these functions you will never again forget to check of the result of such a function. Second, subsequent code (if reached) can always assume that the return value is non-null. For example other than for realloc the following is a valid idiom:

void* p = malloc(small);
defer free(p);
... do something with p ...
defer {
    ... save some precious values from p ...
}
p = defer_realloc(p, huge);

The assignment to p is only executed if the call to defer_realloc is successful, and the call to free in the deferred statement will always use the good (= last allocated) value for p. Also, if it fails execution is immediately transferred to the currently deferred statements, and the necessary terminal actions are taken as indicated, first the second deferred statement may save values from p and then p is freed.

2.7.1 DEFER_MALLOC_WRAP: force the usage of the wrapped allocation functions

Code compiled with this macro set, will replace all usage of the C library storage allocation functions by the wrapped versions as presented above.

3 Terms

3.1 Copyright and license

BSD 3-Clause License

Copyright (c) 2020, Freek Wiedijk
                    Jens Gustedt

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

3.2 Distribution

  • This work is distributed at

https://gustedt.gitlabpages.inria.fr/defer/

  • The sources can be found at

https://gitlab.inria.fr/gustedt/defer

Author: Jens Gustedt

Created: 2020-09-30 Mi 12:05

Validate