Validating adr_data_t
Values
libadr
provides a rich environment for examining and manipulating typed data. However, unlike C's native typing system, the compiler is unaware of libadr
type relationships and is therefore unable to perform static type-checking at compile time. All type checking must be performed at runtime.
The most useful of the type-checking tools provided by libadr
is adr_data_verify
:
boolean_t adr_data_verify(adr_data_t *data, adr_type_t *type, boolean_t recursive);
adr_data_verify takes an adr_data_t
to type-check and an adr_type_t
to type-check against. It can be instructed to check only the adr_data_t
data or data and the transitive closure of every adr_data_t
it references. adr_data_verify
returns B_TRUE
if data matches type, and B_FALSE
if not. If type is NULL
, data is tested against the type it claims to be. Although this method is not a good idea for input validation, it can be useful for error handling.
For data to be verified as type type
, the following must be true:
-
data
must not beNULL
. -
data
must claim to be of typetype
. -
If
type
is an enumeration, data must be a value in that enumeration. -
If
data
is an array, it must be not have been marked invalid by a failedadr_array_add
oradr_array_vset
operation. -
If
data
is an array, it must have noNULL
elements. -
If
data
is an array and recursive is true, each element of the array must satisfy these criteria given the array's element type. -
If
data
is a structure, every non-nullable field must have a value, that is, be non-NULL
. -
If
data
is a structure and recursive is true, every non-NULL
field value must satisfy these criteria considering the field's type.
The adr_data_verify is useful when validating input from an untrusted source. Another, less frequently used application of adr_data_verify, is as a powerful error-handling tool. Suppose you are writing a function that needs to return a complex data value. A traditional way of implementing it would be to check each call for failure individually, as shown in the following example.
Example 3-1 Error Handling Without adr_data_verify
adr_data_t *tmp, *name, *result; if ((name = adr_data_new_struct(name_type)) == NULL) { /* handle failure */ } if ((tmp = adr_data_new_string("Jack")) == NULL) { /* handle failure */ } adr_struct_set(name, "first", tmp); if ((tmp = adr_data_new_string("O'Neill")) == NULL) { /* handle failure */ } adr_struct_set(name, "last", tmp); if ((record = adr_data_new_struct(record_type)) == NULL) { /* handle failure */ } adr_struct_set(record, "name", name); /* ...and so on */
This approach is difficult to implement and difficult to maintain. It is more likely to have a flaw in it than the allocations it is testing are to fail. Instead, using adr_data_verify and the error handling behaviors described in adr_data_t Type, the entire non-truncated function can be reduced to the method shown in the following example.
Example 3-2 Error Handling With adr_data_verify
adr_data_t *name = adr_data_new_struct(name_type); adr_struct_set(name, "first", adr_data_new_string("Jack")); adr_struct_set(name, "last", adr_data_new_string("O'Neill")); adr_data_t *record = adr_data_new_struct(record_type); adr_struct_set(record, "name", name); adr_struct_set(record, "rank", adr_data_new_enum_byname("COLONEL")); adr_struct_set(record, "l_count", adr_data_new_integer(2)); if (!adr_data_verify(record, NULL, B_TRUE)) { /* Recursive type check */ adr_data_free(record); return (NULL); /* NULL means something failed */ } return (record); /* Non-NULL means success */
An important limitation to this technique is that structure fields can be nullable, and the NULL
indicating that the field has no value is indistinguishable from the NULL
that indicates that the allocation of that field's value failed. In such cases, explicitly testing each nullable value's allocation is necessary. Even with such explicit checks, however, the net savings in complexity can be substantial.