A general error handling facility

J

jacob navia

Microsoft proposed recently (In the Technical Report 24173 presented to
the Standards Comitee) a change in the C library in the sense of more
security.

Basically, missing size information is passed to the new primitives like
strcat_s(), gets_s(), and many others.

The good thing in this proposal is that the specifications of the new
library are much more precise than in the existing library, an
improvement that made me start implementing it within the framework of
lcc-win32.

One of the most interesting features of that library is the proposal of
a mechanism for testing the requirements of a function, and the calling
of a routine that the user can modify when a requirement for a function
is missing.

This is a much more attenuated form of the "Programming by contract"
proposed by Betrand Meyer in his Eiffel language. A function call is
seen as a contract within two parties: the calling function, that must
furnish correct arguments to the called function, and the called
function that must furnish specific results to the caller.

In the proposal (I will call it TR 24731 for short) the requirements
of each function are specified in great detail. Let's take, for instance
the function

errno_t strncpy_s(char * restrict s1,
rsize_t s1max,
const char * restrict s2,
rsize_t n);

This function has the following runtime constraints:

1) Neither s1 nor s2 shall be a null pointer.
2) Neither s1max nor n shall be greater than RSIZE_MAX.
3) s1max shall not equal zero.
4) If n is not less than s1max, then s1max shall be greater than
strnlen_s(s2, s1max).
5) Copying shall not take place between objects that overlap.

A simple implementation of this function could be like this:

errno_t strncpy_s(char * restrict DestinationString,
rsize_t DestinationLength,
const char * restrict SourceString,
rsize_t n )
{
errno_t rc = -1;

// strnlen_s is safe to use as it has no rt constraints
rsize_t srcLen = strnlen_s( SourceString, DestinationLength );

// Verify runtime-constraints
if (require( DestinationString != NULL) &&
require(SourceString != NULL) &&
require( DestinationLength <= RSIZE_MAX) &&
require( n <= RSIZE_MAX) &&
((n < DestinationLength) ||
require( srcLen <= DestinationLength-1 )) &&
__check_overlap(DestinationString, DestinationLength,
SourceString, srcLen )) {

for( ; n; --n) {
if( *SourceString == 0 )
break;
*DestinationString++ = *SourceString++;
}
*DestinationString = 0;
rc = 0;
} else {
// Runtime-constraints found, store zero in receiving field
if( (DestinationString != NULL) && DestinationLength > 0 &&
DestinationLength <= RSIZE_MAX ) {
DestinationString[0] = 0;
}
}
return( rc );
}

I have defined the require macro as follows:

#define require(constraint) \
((constraint) ? 1 : ConstraintFailed(__func__,#constraint,NULL) )

Note that this must be a macro since it uses the __func__ identifier,
that is the name of the current function being compiled, in the example
the function strncpy_s.

The "ConstraintFailed" function is defined like this:

#define MSG_MAX 512
int ConstraintFailed( const char *fn, const char *reason,
void *reserved )
{
constraint_handler_t handlerFn = get_constraint_handler_s();
char msg[MSG_MAX];

snprintf(msg,sizeof(msg),
"In function %s, assertion\n%s\nfailed\n",
fn,reason);
handlerFn(msg,reserved,EINVAL);
return 0;
}

The ConstraintFailed function gets the current active handler and calls
it. If the handler returns, it returns zero, what makes the whole series
of tests separated by the && construct fail.

Note that this function will be called only once for the first
constraint that failed, as specified in TR 24731.

What would be interesting is to generalize this mechanism to be able to
use it in other contexts and within other libraries. Obviously this is
not a replacement for try/catch (what lcc-win32 already offers) but it
is *much* better than nothing, as now.

Note that the constraint handlers are set per thread within the context
of lcc-win32 even if this is not specified in the Microsoft proposal.
(You know the tune: "embrace and extend" :)

Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.

True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.

Comments welcome.

jacob
 
I

Ian Collins

jacob navia wrote:

[long explanation snipped]
Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.

True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.

Comments welcome.
Isn't this all rather dangerous?

If a function's contract lists a number of constraints, the function can
be coded assuming these are honoured and can can enforce this though assert.

If you are going to add an option to report rather than abort, you are
opening up a vast can of UB worms - what does the function do if any of
its constraints are violated?
 
J

jacob navia

Ian Collins a écrit :
Isn't this all rather dangerous?

If a function's contract lists a number of constraints, the function can
be coded assuming these are honoured and can can enforce this though assert.

If you are going to add an option to report rather than abort, you are
opening up a vast can of UB worms - what does the function do if any of
its constraints are violated?

Well, if you see the design of strncpy_s in my post you will see
that all constraints are grouped in an if statement.

The else part follows the specifications when the constraints are
NOT there, i.e. what now runs under "unspecified behavior" *IS*
specified, as you can see.

Other functions may have different specs, but in most cases a
meaningful behavior under error is possible. Note that this
happens only if the handler function does NOT abort or make
a long jump out of that context in a pre-established recovery
context...

jacob
 
I

Ian Collins

jacob said:
Ian Collins a écrit :


Well, if you see the design of strncpy_s in my post you will see
that all constraints are grouped in an if statement.

The else part follows the specifications when the constraints are
NOT there, i.e. what now runs under "unspecified behavior" *IS*
specified, as you can see.

Other functions may have different specs, but in most cases a
meaningful behavior under error is possible. Note that this
happens only if the handler function does NOT abort or make
a long jump out of that context in a pre-established recovery
context...
Then how do you differentiate between a constraint than can and one that
can't be violated?

In C++ we'd use a mix of asserts and exceptions to do this, but C lacks
this feature, so how would you report a constraint violation to the
caller without some form of asynchronous callback?

In the cases where meaningful behaviour under error is possible, I'd
have thought this behaviour would involve some form of notification to
the caller.
 
J

jacob navia

Ian Collins a écrit :
Then how do you differentiate between a constraint than can and one that
can't be violated?

In C++ we'd use a mix of asserts and exceptions to do this, but C lacks
this feature, so how would you report a constraint violation to the
caller without some form of asynchronous callback?

In the cases where meaningful behaviour under error is possible, I'd
have thought this behaviour would involve some form of notification to
the caller.

A detected run time constraint violation provokes calling the handler
function. IF the handler returns (if it doesn't call abort) all "safer"
functions return a negative value.

In case there were NO constraint violations, all those functions return
zero.
 
I

Ian Collins

jacob said:
Ian Collins a écrit :


A detected run time constraint violation provokes calling the handler
function. IF the handler returns (if it doesn't call abort) all "safer"
functions return a negative value.

In case there were NO constraint violations, all those functions return
zero.

Yes, but how can the caller be notified? A handler in isolation isn't a
great deal of use.

That's the key with exceptions, a run time exception condition can be
passed back to the caller.
 
R

Richard Bos

Ian Collins said:
jacob navia wrote:

[long explanation snipped]
Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.

True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.
Isn't this all rather dangerous?

If a function's contract lists a number of constraints, the function can
be coded assuming these are honoured and can can enforce this though assert.

If you are going to add an option to report rather than abort, you are
opening up a vast can of UB worms - what does the function do if any of
its constraints are violated?

Punt to the try/exception handler, of course.

What, you mean that doesn't exist in ISO C? Gosh.

[ Follow-ups set to where they should go. ]

Richard
 
J

jacob navia

Richard said:
jacob navia wrote:

[long explanation snipped]
Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.

True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.

Isn't this all rather dangerous?

If a function's contract lists a number of constraints, the function can
be coded assuming these are honoured and can can enforce this though assert.

If you are going to add an option to report rather than abort, you are
opening up a vast can of UB worms - what does the function do if any of
its constraints are violated?


Punt to the try/exception handler, of course.

What, you mean that doesn't exist in ISO C? Gosh.

[ Follow-ups set to where they should go. ]

Richard

And that is all you have to say?

Impressing.

:)

jacob
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,539
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top