Error types

From vegard.wiki
Revision as of 20:44, 18 December 2019 by Vegard (talk | contribs) (new page)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The Linux kernel often uses small positive integers as return values to indicate errors. For functions returning pointers, special macros are used for converting these integers into special pointer values (ERR_PTR()) and back again (IS_ERR()/PTR_ERR()).

However, this usage is incredibly error prone. There are several bugs of this type per month, e.g. [1][2][3][4][5][6][7] to give some recent examples.

It is fully possible to create a safe (and equally efficient) alternative in C++, similar to Rust's Result type, which does not allow you to dereference an error value.

Definitions:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <functional>

template<typename T>
class errptr {
        unsigned long raw_value;

public:
        // Constructor for errors
        explicit errptr(int error_code):
                raw_value(-error_code)
        {
        }

        // Constructors for (real) pointers
        explicit errptr(T *ptr):
                raw_value((long) ptr)
        {
        }

        // unwrap pointer/error
        template<typename Success, typename Failure>
        inline void check(Success success, Failure failure)
        {
                if ((long) raw_value > 0)
                        success((T *) raw_value);
                else
                        failure(-(int) raw_value);
        }
};

Usage:

struct kthread {
};

errptr<kthread> kthread_create(int x)
{
        if (x)
                return errptr<kthread>(new kthread());

        return errptr<kthread>(EINVAL);
}

int main(int argc, char *argv[])
{
        if (argc < 2) {
                fprintf(stderr, "usage: %s NUM\n", argv[0]);
                exit(EXIT_FAILURE);
        }

        errptr<kthread> e = kthread_create(atoi(argv[1]));
        e.check([](kthread *thread){
                // success
                printf("succeeded with pointer = %p\n", thread);
        }, [](int error_code) {
                // failure
                printf("failed with error code %d\n", error_code);
        });

        return 0;
}

In practice, the lambdas are inlined into the caller and so do not produce worse code than the equivalent open-coded checks.