Showing posts with label Diagnostics. Show all posts
Showing posts with label Diagnostics. Show all posts

Friday, December 27, 2024

Predefined System error enumerations

Overview
The standard library provides predefined enumeration classes to define error conditions for these categories: generic_category, iostream_category and future_categoty.

Details
The following discusses  some of the predefined error conditions and error codes. They are organized into a enum class for each category. 
In addition, helper classes is_error_condition_enum  or is_error_condition_enum are defined to indicate if they are defined for a error_code or error_condition
The enumerated value is used by helper functions  make_error_code() and make_error_condition() to create a error_code or a error_condtion.

is_error_code_enum
This is a traits class to identify whether a particular type is an error code enum type, and thus can be used to construct or assign values to objects of type error_code.
Syntax
template <class T>
struct is_error_code_enum : public false_type {};
The standard header only provides the default definition, which simply inherits from false_type. But it should be specialized as inheriting from true_type to enable the construction of error_code object from error code enum types. The standard error condition types  io_errc and future_errc inherit true_type.

is_error_condition_enum
This is a traits class to identify whether a particular type is an error condition enum type, and thus can be used to construct or assign values to objects of type error_condition.
Syntax
template <class T>
struct is_error_condition_enum : public false_type {};
The standard header only provides the default definition, which simply inherits from false_type. But it should be specialized as inheriting from true_type to enable the construction of error_condition object from error condition enum types. The standard error condition type errc inherit true_type.

make_error_code()
Syntax
make_error_code(econd_enum e)

Creates an error_code for econd_enum e and returns it.
It's equivalent to error_code(static_cast<int>(e), cat) where cat is the category for which econd_enumaration enum is defined.

make_error_condition()
Syntax
make_error_condition(econd_enum e)

Creates an error_condition for econd_enumaration enum e and returns it.
It's equivalent to error_condition(static_cast<int>(e), cat) where cat is the category for which econd_enumaration enum is defined.
used to construct or assign values to objects of type error_code.

errc
enum class with enumerators for the error_conditions with one to one mapping to error codes from POSIX error codes which are found in <cerrno> header file. 
Type trait class is_error_condition_enum is implemented returning  true, indicating that it's an error_condition enumeration. 
Helper functions make_error_code() and make_error_condition() are also implemented.
error_condition ecnd = make_error_condition(errc::invalid_argument);
//same as
//error_condition ecnd = make_error_condition(EINVAL,  std::generic_category());
The complete list can be viewed here. 

void print_error(const std::string& details, std::error_code error_code)
{
    std::string value_name;
    if (error_code == std::errc::invalid_argument)
        value_name = "std::errc::invalid_argument";
    if (error_code == std::errc::no_such_file_or_directory)
        value_name = "std::errc::no_such_file_or_directory";
    if (error_code == std::errc::is_a_directory)
        value_name = "std::errc::is_a_directory";
    if (error_code == std::errc::permission_denied)
        value_name = "std::errc::permission_denied";
 
    std::cerr << details << ":\n  "
              << std::quoted(error_code.message())
              << " (" << value_name << ")\n\n";
}
 
void print_errno(const std::string& details, int errno_value = errno)
{
    print_error(details, std::make_error_code(std::errc(errno_value)));
}
 
int main()
{
    /*prints
    Detaching a not-a-thread...
    Error detaching empty thread:
        "Invalid argument" (std::errc::invalid_argument)
    */
    std::cout << "Detaching a not-a-thread...\n";
    try
    {
        std::thread().detach();
    }
    catch (const std::system_error& e)
    {
        print_error("Error detaching empty thread", e.code());
    }
 
    /*prints
    Opening nonexistent file...
    Error opening nonexistent file for reading:
        "No such file or directory" (std::errc::no_such_file_or_directory)
    */
    std::cout << "Opening nonexistent file...\n";
    std::ifstream nofile{"nonexistent-file"};
    if (!nofile.is_open())
        print_errno("Error opening nonexistent file for reading");
 
    /*prints
    Reading from directory as a file...
    Error reading data from directory:
        "Is a directory" (std::errc::is_a_directory)    
    */
    std::cout << "Reading from directory as a file...\n";
    std::filesystem::create_directory("dir");
    std::ifstream dir_stream{"dir"};
    [[maybe_unused]] char c = dir_stream.get();
    if (!dir_stream.good())
        print_errno("Error reading data from directory");
 
    /*prints
    Open read-only file for writing...
    Error opening read-only file for writing:
        "Permission denied" (std::errc::permission_denied)    
    */
    std::cout << "Open read-only file for writing...\n";
    std::fstream{"readonly-file", std::ios::out};
    std::filesystem::permissions("readonly-file", std::filesystem::perms::owner_read);
    std::fstream write_readonly("readonly-file", std::ios::out);
    if (!write_readonly.is_open())
        print_errno("Error opening read-only file for writing");

future_errc
enum class with enumerators for error_codes  for future errors. 
Type trait class  is_error_code_enum is implemented  returning  true,  indicating that it's an error_code enumeration. 
Helper functions make_error_code() and make_error_condition() are also implemented.
error_condition ecnd = make_error_condition(future_errc::future_already_retrieved);
//same as
//error_condition ecnd = make_error_condition(1,  std::future_category());
The complete list can be viewed here.

    std::promise<int> prom;
    //prints:[future already retrieved]
    try 
    {
        prom.get_future();
        prom.get_future();   // throws std::future_error with future_already_retrieved
    }
    catch (std::future_error& e) 
    {
        if (e.code() == std::make_error_condition(std::future_errc::future_already_retrieved))
            std::cerr << "[future already retrieved]\n";
        else 
            std::cerr << "[unknown exception]\n";
    }

io_errc
enum class with enumerators for io stream errors. 
Type trait class  is_error_code_enum is implemented  returning  true, indicating that it's an error_code enumeration. 
Helper functions make_error_code() and make_error_condition() are also implemented.
error_condition ecnd = make_error_condition(io_errc::stream);
//same as
//error_condition ecnd = make_error_condition(1,  std::iostream_category());
The complete list can be viewed here.
    //prints:failure caught: stream error condition
    try 
    {
        std::cin.rdbuf(nullptr);    // throws
        std::cin.exceptions (std::ios::failbit|std::ios::badbit);
    } 
    catch (std::ios::failure& e) 
    {
        std::cerr << "failure caught: ";
        if ( e.code() == std::make_error_condition(std::io_errc::stream) )
            std::cerr << "stream error condition\n"; 
        else
            std::cerr << "some other error condition\n";
    }


Wednesday, December 25, 2024

System Errors

Overview
System error numbers are different in different platforms. e.g., file system errors.  In C++11,  a new exception class system_error along with helper classes - error_category,  error_codeerror_condition 
were introduced to the diagnostic infrastructure to bring uniformity and develop cross platform applications.

Details
system_error
This exception was added in C++11 to report exceptions from Operating System and low level interfaces.
The class inherits from runtime_error. In addition to what()  it adds code() that returns of type error_code to gather the error information.

Constructors
NameDescription
  1. system_error
    (error_code ec, const string& s)
  2. system_error
    (error_code ec, const char* cs)
  3. system_error
    (error_code ec)
  4. system_error
    (int ev, const error_category& cat, const string& s)
  5. system_error
    (int ev, const error_category& cat, const char* cs)
  6. system_error
    (int val, const error_category& cat)
  1. Constructs with error code ec and explanation string s. The string returned by what() is guaranteed to contain s as a substring.
  2. Constructs with error code ec and explanation cstring cs.
  3. Constructs with error code ec.
  4. Constructs with underlying error code ev and associated error category cat along with  explanation string s.
  5. Constructs with underlying error code ev and associated error category cat along with  explanation cstring cs. 
  6. Constructs with underlying error code ev and associated error category cat.
Example
    //1
    system_error(error_code{io_errc::stream},string("hello world!"));
    //2
    system_error(error_code{io_errc::stream},"hello world!");
    //3
    system_error(error_code{io_errc::stream});
    //4
    system_error(1,iostream_category(),string("hello world!"));
    //5
    system_error(1,iostream_category(),"hello world!");
    //6
    system_error(1,iostream_category());

Methods
NameDescription
const error_code& code()Returns the error_code object associated with the exception.

Example
auto se = system_error(error_code{io_errc::stream},string("hello world!"));
//prints:1
cout << se.code().value();
const char* what()
Returns the message that describes the exception.
This message includes the string used on construction as s, and -possibly- additional information, such as the message associated with the error_code

Example
auto se = system_error(error_code{io_errc::stream},string("hello world!"));
//prints:hello world!: iostream error
cout << se.what();

error_code
An error_code object holds an platform dependent error code value associated with a platform independent category.
The operating system and other low-level applications and libraries generate numerical error codes to represent possible results. These numerical values may carry essential information for a specific platform, but be non-portable from one platform to another. 
For example the returned value for  "path does not exist" error  in POSIX platforms is 2 (ENOENT). In Windows it's 3 (ERROR_PATH_NOT_FOUND).
error_code objects associate such numerical codes to error categories, so that they can be interpreted when needed as more abstract (and portable) error conditions.

The following describes the class in more detail.
Constructors
NameDescription
  1. error_code() 
  2. error_code
    (int ev, const error_category& cat) 
  3. error_code
    (ErrorCodeEnum e)
  1. Initializes the object equivalent to error_code(0,system_category()) 
  2. Initializes the object with error code ev and category cat
  3. Initializes the object equivalent to make_error_code(e) where is_error_code_enum<decltype(e)>::value  == true_type.
Example
void print_errorcode(error_code e)
{
    cout << "Code: " << e.value() << endl;
    cout << "Category: " << e.category().name() << endl;
    cout << "Message: " << e.message() << endl;
    cout << endl;
}

    //1
    auto ec = error_code{};
    /*prints
    Code: 0
    Category: system
    Message: Success
    */
    print_errorcode(ec);

    //2
    ec = error_code{ENOENT,generic_category()};
    /*prints
    Code: 2
    Category: generic
    Message: No such file or directory
    */
    print_errorcode(ec);

    //3
    ec = make_error_code(io_errc::stream);
    /*prints
    Code: 1
    Category: iostream
    Message: iostream error
    */
    print_errorcode(ec);

Methods
NameDescription
void assign
(int ev, const error_category& cat) 
 Assigns the error_code object a value of ev associated with the error_category cat.

Example
    error_code ec;

    auto fh = open( "noname.c", O_RDONLY );
    if( fh == -1 )
    {
        ec.assign(errno,generic_category());
    }
    /*prints
    Code: 2
    Category: generic
    Message: No such file or directory
    */
    print_errorcode(ec);
error_code& operator= (ErrorCodeEnum e) Calls make_error_code() to construct an error code from e, whose value is assigned to the error_code object.
Note that is_error_code_enum<decltype(e)>::value  == true_type

Example
    error_code ec;
    ifstream ifs("noname.c");
    if( !ifs )
    {
        ec = io_errc::stream;
    }
    /*prints
    Code: 1
    Category: iostream
    Message: iostream error
    */
print_errorcode(ec);
void clear()Clears the value in the error_code object so that it is set to a value of 0 of the system_category (indicating no error).

Example
    error_code ec;
    ec = io_errc::stream;
    ec.clear();
    /*prints
    Code: 0
    Category: system
    Message: Success
    */
    print_errorcode(ec);
int value()Returns the error value associated with the error_code object.
const error_category& category()Returns a reference to the error category associated with the error_code object.
string message()
Error messages are defined by the category the error code belongs to.
This is same as category().message(value()).
error_condition default_error_condition()
Returns the default error_condition object associated with the error_code object.
Same as category().default_error_condition(value()).
operator bool()
Returns whether the error code has a numerical value other than 0.
If it is zero (which is generally used to represent no error), the function returns false, otherwise it returns true.
basic_ostream& operator<<
(basic_ostream& os, const error_code& ec)
Writes a textual representation of the error code, producing the same output as the following operation:
os << ec.category.name() << ':' << ec.value();

Example
//prints:iostream:1
auto ec = make_error_code(io_errc::stream);
cout << ec;

External Methods
NameDescription
bool operator ==
(const error_code& lhs, const error_code& rhs)
compares two error codes for equality. Internally evaluated as (lhs.category()==rhs.category() && lhs.value()==rhs.value()).

Example
    auto ec = make_error_code(io_errc::stream);
    auto ec2 = error_code(1,iostream_category());

    //b:true
    auto b = (ec == ec2);
bool operator !=
(const error_code& lhs, const error_code& rhs)
compares two error codes for inequality. Evaluated as !(lhs == rhs).

Example
    auto ec = make_error_code(errc::file_exists);
    auto ec2 = error_code{ERROR_FILE_EXISTS,system_category()};

    //b:true
    b = (ec != ec3);    

error_condition 
error_condition is a platform-independent error code. Like error_code, it is uniquely identified by an integer value and a error_category, but unlike error_code, the value is not platform-dependent.
Therefore, it can be compared against a value. A typical implementation holds a value and a pointer to an error_category. An error_codition can be created with make_error_condition() by passing appropriate argument. For example,  make_error_condition(errc::invalid_argument).
Because error_condition objects can be compared with error_code objects directly by using relational operators, error_condition objects are generally used to check whether a particular error_code obtained from the system matches a specific error condition no matter the system.
The categories associated with the error_condition and the error_code define the equivalences between them.

Constructors
NameDescription
  1. error_condition() 
  2. error_condition
    (int val, const error_category& cat) 
  3. error_condition
    (ErrorCodeEnum e)
  1. Initializes the object equivalent to error_condition(0,generic_category()) 
  2. Initializes the object with error code val and category cat
  3. Initializes the object equivalent to make_error_condition(e) where is_error_condition_enum<decltype(e)>::value  == true_type.


Example
void print_errorcondition(error_condition e)
{
    cout << "Code: " << e.value() << endl;
    cout << "Category: " << e.category().name() << endl;
    cout << "Message: " << e.message() << endl;
    cout << endl;
}

    //1
    auto ec = error_condition{};
    /*prints
    Code: 0
    Category: generic
    Message: Success
*/ print_errorcondition(ec); //2 ec = error_condition{ENOENT,generic_category()}; /*prints
    Code: 2
    Category: generic
    Message: No such file or directory
*/ print_errorcondition(ec); //3 ec = make_error_condition(io_errc::stream); /*prints Code: 1 Category: iostream Message: iostream error */ print_errorcondition(ec);

Methods
NameDescription
void assign
(int val, const error_category& cat) 
Assigns the error_condition object a value of val associated with the error_category cat.

Example
    error_condition ec;

    auto fh = open( "noname.c", O_RDONLY );
    if( fh == -1 )
    {
        ec.assign(errno,generic_category());
    }
    /*prints
    Code: 2
    Category: generic
    Message: No such file or directory
    */
    print_errorcondition(ec);
error_condition& operator= (ErrorCodeEnum e) Calls make_error_condition() to construct an error error_condition from e, whose value is assigned to the error_condition object.
Here is_error_condition_enum<decltype(e)>::value  == true_type.

Example
    error_condition ec;
    ifstream ifs("noname.c");
    if( !ifs )
    {
        ec = errc::no_such_file_or_directory;
    }
    /*prints
    Code: 2
    Category: generic
    Message: No such file or directory
    */
    print_errorcondition(ec);
void clear()Clears the value in the error_condition object so that it is set to a value of 0 of the generic_category (indicating no error).

Example
    error_condition ec;
    ec = errc::no_such_file_or_directory;
    ec.clear();
    /*prints
    Code: 0
    Category: generic
    Message: Success
    */
    print_errorcondition(ec);
int value() Returns the error value associated with the error_condition object.
const error_category& category()Returns a reference to the error category associated with the error_condition  object.
string message()
Error messages are defined by the category the error_condition  belongs to.
This is same as category().message(value()).
operator bool()
Returns whether the error_condition  has a numerical value other than 0.
If it is zero (which is generally used to represent no error), the function returns false, otherwise it returns true.

External Methods
NameDescription
  1. bool operator ==
    (const error_condition& lhs,
    const error_condition& rhs)
  2. bool operator ==
    (const error_condition& lhs,
    const error_code& rhs)
  1. compares two error_conditions for equality. Internally evaluated as (lhs.category()==rhs.category() && lhs.value()==rhs.value())
  2. compares an error code and error conditions for equality. Internally evaluated as lhs.category().equivalent(lhs.value,rhs) || rhs.category().equivalent(lhs,rhs.value())
Example
    //1
    auto econd = make_error_condition(io_errc::stream);
    auto econd2 = error_condition(1,iostream_category());
    //b:true
    bool b = (econd == econd);
    
    //2
    auto ec = io_errc::stream;
    //b:true
    b = (ec == econd);
  1. bool operator!=
    (const error_condition& lhs,
    const error_condition& rhs)
  2. bool operator!=
    (const error_condition& lhs,
    const error_code& rhs)
  1. compares two error_conditions for inequality. Evaluated as !(lhs == rhs).
  2. compares an error code and error conditions for inequality.  Evaluated as !(lhs == rhs).
Example
    //1
    auto econd = errc::file_exists;
    auto econd2 = error_condition{EEXIST,system_category()};
    //b:true
    bool b = (econd != econd2);
    
    //2
    auto ec = make_error_code(errc::file_exists);
    //b:true
    b = (ec != econd2);    

Using error_code and error_condition
As discussed earlier, in some cases,  error_code is platform dependent value and error_condition is platform independent value. Thus it's not appropriate to compare the result of an operation such as a system call that returns an error_code directly. 

For example, the following code would be fine since error_code defines explicit bool() operator to ascertain if there were errors.
boost::system::error_code ec;
create_directory("", ec);
if (!ec)
{
  // Success.
}
else
{
  // Failure.
}

However going deeper and examining the returned values as below, it's not advisable to use the returned values directly as they are platform dependent. For example the returned value of ec.value() call  in POSIX platforms is ENOENT (2) and in Windows is ERROR_PATH_NOT_FOUND (3). Therefore use of  error_code.value() should be avoided if there platform dependency.
boost::system::error_code ec;
create_directory("", ec);
//linux
if (ec.value() == ENOENT) // No!
//windows
if (ec.value() == ERROR_PATH_NOT_FOUND) // No!

instead error_code should be compared with error_condition  as shown below.
boost::system::error_code ec;
create_directory("", ec); if (ec == std::errc::no_such_file_or_directory)

In the above code, internally the if condition is evaluated as below: 
ec.category().equivalent(ec.value(), std::errc::no_such_file_or_directory)
This is depicted in  example 10 (POSIX) and example 11 (Windows).

error_category
This type serves as a base class for specific category types.
Category types are used to identify the source of an error. They also define the relation between error_code and error_condition objects of its category, as well as the message set for error_code objects.
As noted earlier, an error_category is used to define sources of errors or categories of error codes and error_conditions. 
It also provides  method equivalent() that provides mapping between  error_codes and 
and error_conditions. In addition, it provides a method   default_error_condition() to return error_condition for a error_code.

Standard library predefines following categories.
NameDescription
generic_categoryidentifies the generic error category. The generic category represents the error values of the portable subset of errno values defined by the POSIX standard, whereas the system category is OS dependent.
system_categoryidentifies the operating system error category.  Under POSIX, the system category represents the errno values returned by the OS APIs (a superset of those in the generic category), whereas under Windows, the system category represents the error values returned by GetLastError().
iostream_category  identifies the iostream error category.
future_categoryidentifies the future error category. Even though future_error exception is not derived from system_error, it supports error_code

Methods
NameDescription
const char* name()In derived classes, the function returns a C-string naming the category.

For a generic_category object, it returns "generic".
For a system_category object, it returns "system".
For a iostream_category object, it returns "iostream".
For a future_category object, it returns "future".
error_condition default_error_condition
(int val)
Returns the default error_condition object of this category that is associated with the error_code identified by a value of val.
Returns the error condition for the given error value.

Equivalent to error_condition(val, *this).

Classes derived from error_category may override this function to map certain error values to a generic category. For example, system_category overrides this function to map the error values that match POSIX errno values to generic_category.
  1. bool equivalent
    (int e, const error_condition& c)
  2. bool equivalent
    (const error_code& e, int c)
Checks whether error code is equivalent to an error condition for the error category represented by *this.
  1. Equivalent to default_error_condition(e) == c.
  2. Equivalent to *this == e.category() && e.value() == c.
string message
(int val) 
 Returns a string object with a message describing the error condition denoted by val.

This function is called both by error_code::message and error_condition::message to obtain the corresponding message in the category. Therefore, numerical values used by custom error codes and error conditions should only match for a category if they describe the same error.

Usage examples
As noted above an error_code in the exception should be always compared to an  error_condition from the same category that can be created by make_error_condition().

future_category
The example 12  depicts the usage.

iostream_category
The example 13 depicts the usage.

generic_category
The  example 14   depicts the usage.

Custom error_category
Application can define their own categories to throw custom error_codes in the exceptions.
This is discussed in detail below.
The following example 15 discusses an implementation of a custom error_category class to handle http protocol errors such as 404 (page not found) as below.
  • type trait class is_error_condition_enum is implemented returning  true, indicating that it's an error_condition enumeration. 
  • Custom enum class is defined enumerate error_conditions. e.g., client_error.
  • make_error_condition()  is overridden to return error_condition object for an enumeration for this category. e.g., client_error.
  • It basically derives from error_category class. 
  • It defines a set of error numbers commonly indicate http errors such as error code 404.
  • It overrides default_error_condition() to return a error_condition for a error number. e.g., returns error_condition, client_error for error code 404 .
  • It overrides equivalent() to check if the error number in the error_code matches with the error condition. e.g., error code 404 with client_error.
  • It overrides message() to return to text message for error number. for e.g., 404.
// custom error conditions enum type:
enum class custom_errc { success=0, client_error, server_error, other };
namespace std 
{
    template<> struct is_error_condition_enum<custom_errc> : public true_type {};
}

// custom category:
struct custom_category_t : public std::error_category 
{
    virtual const char* name() const noexcept  { return catname; }
    virtual std::error_condition default_error_condition  (int ev) const noexcept 
    {
        if ((ev>=200)&&(ev<300)) return std::error_condition(custom_errc::success);
        else if ((ev>=400)&&(ev<500)) return std::error_condition(custom_errc::client_error);
        else if ((ev>=500)&&(ev<600)) return std::error_condition(custom_errc::server_error);
        else return std::error_condition(custom_errc::other);
    }
    virtual bool equivalent (const std::error_code& code, int condition) const noexcept 
    {
        return *this==code.category() &&
        static_cast<int>(default_error_condition(code.value()).value())==condition;
    }
    virtual std::string message(int ev) const 
    {
        switch (ev) 
        {
            case 200: return "OK";
            case 403: return "403 Forbidden";
            case 404: return "404 Not Found";
            case 500: return "500 Internal Server Error";
            case 503: return "503 Service Unavailable";
            default: return "Unknown error";
        }
    }
    const char *catname = "custom_http";
} custom_category;

// make_error_code overload to generate custom conditions:
std::error_condition make_error_condition (custom_errc e) 
{
    return std::error_condition(static_cast<int>(e), custom_category);
}

void readapage(const char* url)
{
    if (std::string(url) == "no url")
        throw (std::system_error(std::error_code(404, custom_category)));
}

int main()
{
   //prints:client Error: 404 Not Found
    try
    {
        readapage("no url");
    }
    catch(const std::system_error& ex)
    {
        auto &e = ex.code();
        if (e == custom_errc::success) cout << "Success: " << e.message() << endl;
        else if (e == custom_errc::client_error) cerr << "Client Error: " << e.message() << endl;
        else if (e == custom_errc::server_error) cerr << "Server Error: " << e.message() << endl;
        else cerr << "Unknown" << endl;;
    }