Skip to content

Commit

Permalink
RFE #68 - nullok annotation.
Browse files Browse the repository at this point in the history
  • Loading branch information
apnadkarni committed Nov 24, 2021
1 parent b2b6bee commit a1f0fa5
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 135 deletions.
50 changes: 32 additions & 18 deletions doc/concepts.ruff
Expand Up @@ -191,8 +191,19 @@ namespace eval Concepts {

Null pointers cannot be registered and are not checked for registration
even when passed as safe pointers in function calls. However, the tag is
verified if present. The literal `NULL` can be used to represent a null
pointer.
verified if present.

The literal `NULL` can be used to represent a null pointer. By default,
a function result is a null pointer, it is treated as an error and
triggers the function's error handling mechanisms. Similarly, an attempt
to pass a null pointer to a function or store it as a field value
in a C struct will raise an exception. This can be overridden by
including the `nullok` annotation on the function return, parameter
or structure fields type definition.

Note that when returned as as output parameters from function,
either directly or embedded as struct fields, null pointers are
permitted even without the `nullok` annotation.

#### Memory operations

Expand Down Expand Up @@ -264,7 +275,7 @@ namespace eval Concepts {
- Function return values cannot be declared as `chars` or `unichars` as
C itself does not support array return values. Generally, functions
typed as returning `char *` need to be declaring as returning `pointer`
as the pointers have to be explcitly managed. Only in the specific cases
as the pointers have to be explicitly managed. Only in the specific cases
where the returned pointer is static or does not need to be disposed of
for some other reason, the return value can be typed as `string` or
`unistring`.
Expand Down Expand Up @@ -579,32 +590,35 @@ namespace eval Concepts {

Two additional sets of type annotations are provided to solve these
issues. The first set of annotations is used to define the error check
conditions to be applied to function return values. The second set is
used to specify how the error detail is to be retrieved.
conditions to be applied to function return values. The
second set is used to specify how the error detail is to be retrieved.

The annotations for error checking are:
The following annotations for error checking can be used for
**integer** return values.

`zero` - The value must be zero.
`nonzero` - The value must be non-zero.
`nonnegative` - The value must be zero or greater.
`positive` - The value must be greater than 0.

These annotations may only be used with integer types except `nonzero`
which can also be used for `pointer`, `string` and `unistring` types.
In the case of the latter two, returned `char *` values that are `NULL`
pointers are transformed to empty strings by default. The use of
the `nonzero` annotation will force an exception to be generated instead.

At most one of the above annotations can be attached to a return type.
The function value is then checked whether the corresponding
condition is met. Failure to do so is treated as an error condition.

An error condition results in an exception being generated unless the
`onerror` annotation is specified (see below).
However, the default error message generated is
generic and does not provide detail about why the error occured. The
following error retrieval annotations specify how detail about the error
is to be obtained.
An error condition is also generated when a function returning
a pointer returns a null pointer. This is also true for `string` and
`unistring` return types since those are pointers beneath the covers.
This treatment of null pointers as errors can be overridden with the
the `nullok` annotation. If this annotation is specified, a null
pointer return value, or an empty string in the case of `string` and
`unistring` types, will be returned to the caller.

An error condition arising from one of the error checking annotations or
a null pointer results in an exception being generated unless the
`onerror` annotation is specified (see below). However, the default
error message generated is generic and does not provide detail about why
the error occured. The following error retrieval annotations specify how
detail about the error is to be obtained.

`errno` - The POSIX error is stored in `errno`. The error message is
generated using the C runtime `strerror` function.
Expand Down
5 changes: 2 additions & 3 deletions generic/tclCffiDyncall.c
Expand Up @@ -741,9 +741,8 @@ CffiFunctionCall(ClientData cdata,
case CFFI_K_TYPE_UNISTRING:
pointer = dcCallPointer(vmP, fnP->fnAddr);
/* Do IMMEDIATELY so as to not lose GetLastError */
if (protoP->returnType.typeAttrs.flags & CFFI_F_ATTR_REQUIREMENT_MASK)
fnCheckRet = CffiCheckPointer(
ip, &protoP->returnType.typeAttrs, pointer, &sysError);
fnCheckRet = CffiCheckPointer(
ip, &protoP->returnType.typeAttrs, pointer, &sysError);
CffiPointerArgsDispose(ip, protoP, callCtx.argsP, fnCheckRet); \
switch (protoP->returnType.typeAttrs.dataType.baseType) {
case CFFI_K_TYPE_POINTER:
Expand Down
1 change: 1 addition & 0 deletions generic/tclCffiInt.h
Expand Up @@ -138,6 +138,7 @@ typedef struct CffiTypeAndAttrs {
#define CFFI_F_ATTR_ENUM 0x1000000 /* Use enum names */
#define CFFI_F_ATTR_BITMASK 0x2000000 /* Treat as a bitmask */
#define CFFI_F_ATTR_NULLIFEMPTY 0x4000000 /* Empty value -> null pointer */
#define CFFI_F_ATTR_NULLOK 0x8000000 /* Null pointers permissible */
} CffiTypeAndAttrs;

/* Attributes allowed on a parameter declaration */
Expand Down
10 changes: 10 additions & 0 deletions generic/tclCffiTest.c
Expand Up @@ -676,6 +676,16 @@ EXTERN void structArrayExchange (struct SimpleStruct *a, struct SimpleStruct *b,
}
}

struct StructWithPointer {
void *p;
};
EXTERN void makeStructWithPointer(struct StructWithPointer *strucP, void *p) {
strucP->p = p;
}
EXTERN void *extractStructWithPointer(struct StructWithPointer *strucP) {
return strucP->p;
}

struct StructWithNestedArrays {
struct SimpleStruct s[3];
void *p[3];
Expand Down
84 changes: 48 additions & 36 deletions generic/tclCffiTypes.c
Expand Up @@ -83,24 +83,26 @@ const struct CffiBaseTypeInfo cffiBaseTypes[] = {
/* */
{TOKENANDLEN(pointer),
CFFI_K_TYPE_POINTER,
CFFI_F_ATTR_PARAM_MASK | CFFI_F_ATTR_SAFETY_MASK | CFFI_F_ATTR_NONZERO
CFFI_F_ATTR_PARAM_MASK | CFFI_F_ATTR_SAFETY_MASK | CFFI_F_ATTR_NULLOK
| CFFI_F_ATTR_LASTERROR | CFFI_F_ATTR_ERRNO | CFFI_F_ATTR_ONERROR,
sizeof(void *)},
{TOKENANDLEN(string),
CFFI_K_TYPE_ASTRING,
/* Note string cannot be INOUT parameter */
CFFI_F_ATTR_IN | CFFI_F_ATTR_OUT | CFFI_F_ATTR_BYREF | CFFI_F_ATTR_NULLIFEMPTY | CFFI_F_ATTR_NONZERO
| CFFI_F_ATTR_LASTERROR | CFFI_F_ATTR_ERRNO | CFFI_F_ATTR_ONERROR,
/* Note string cannot be INOUT parameter */
CFFI_F_ATTR_IN | CFFI_F_ATTR_OUT | CFFI_F_ATTR_BYREF
| CFFI_F_ATTR_NULLIFEMPTY | CFFI_F_ATTR_NULLOK | CFFI_F_ATTR_LASTERROR
| CFFI_F_ATTR_ERRNO | CFFI_F_ATTR_ONERROR,
sizeof(void *)},
{TOKENANDLEN(unistring),
CFFI_K_TYPE_UNISTRING,
/* Note unistring cannot be INOUT parameter */
CFFI_F_ATTR_IN | CFFI_F_ATTR_OUT | CFFI_F_ATTR_BYREF | CFFI_F_ATTR_NULLIFEMPTY | CFFI_F_ATTR_NONZERO
| CFFI_F_ATTR_LASTERROR | CFFI_F_ATTR_ERRNO | CFFI_F_ATTR_ONERROR,
/* Note unistring cannot be INOUT parameter */
CFFI_F_ATTR_IN | CFFI_F_ATTR_OUT | CFFI_F_ATTR_BYREF
| CFFI_F_ATTR_NULLIFEMPTY | CFFI_F_ATTR_NULLOK | CFFI_F_ATTR_LASTERROR
| CFFI_F_ATTR_ERRNO | CFFI_F_ATTR_ONERROR,
sizeof(void *)},
{TOKENANDLEN(binary),
CFFI_K_TYPE_BINARY,
/* Note binary cannot be OUT or INOUT parameters */
/* Note binary cannot be OUT or INOUT parameters */
CFFI_F_ATTR_IN | CFFI_F_ATTR_BYREF,
sizeof(unsigned char *)},
{TOKENANDLEN(chars),
Expand Down Expand Up @@ -194,7 +196,8 @@ enum cffiTypeAttrOpt {
STOREALWAYS,
ENUM,
BITMASK,
ONERROR
ONERROR,
NULLOK,
};
typedef struct CffiAttrs {
const char *attrName; /* Token */
Expand Down Expand Up @@ -267,6 +270,11 @@ static CffiAttrs cffiAttrs[] = {
2},
{"bitmask", BITMASK, CFFI_F_ATTR_BITMASK, CFFI_F_TYPE_PARSE_PARAM, 1},
{"onerror", ONERROR, CFFI_F_ATTR_ONERROR, CFFI_F_TYPE_PARSE_RETURN, 2},
{"nullok",
NULLOK,
CFFI_F_ATTR_NULLOK,
CFFI_F_TYPE_PARSE_RETURN | CFFI_F_TYPE_PARSE_PARAM | CFFI_F_TYPE_PARSE_FIELD,
1},
{NULL}};


Expand Down Expand Up @@ -917,6 +925,9 @@ CffiTypeAndAttrsParse(CffiInterpCtx *ipCtxP,
Tcl_IncrRefCount(fieldObjs[1]);
typeAttrP->parseModeSpecificObj = fieldObjs[1];
break;
case NULLOK:
flags |= CFFI_F_ATTR_NULLOK;
break;
}
}

Expand Down Expand Up @@ -1026,7 +1037,10 @@ CffiTypeAndAttrsParse(CffiInterpCtx *ipCtxP,
goto invalid_format;
}
if ((flags & CFFI_F_ATTR_ONERROR)
&& (flags & CFFI_F_ATTR_REQUIREMENT_MASK) == 0) {
&& (flags & CFFI_F_ATTR_REQUIREMENT_MASK) == 0
&& baseType != CFFI_K_TYPE_POINTER
&& baseType != CFFI_K_TYPE_ASTRING
&& baseType != CFFI_K_TYPE_UNISTRING) {
message = "\"onerror\" requires an error checking annotation.";
goto invalid_format;
}
Expand Down Expand Up @@ -1362,17 +1376,16 @@ CffiPointerArgsDispose(Tcl_Interp *ip,
}

/* Function: CffiCheckPointer
* Checks if a pointer has any associated error checks.
* Checks if a pointer meets requirements annotations.
*
* Parameters:
* ip - interpreter
* typeAttrsP - descriptor for type and attributes
* pointer - pointer value to wrap
* pointer - pointer value to check
* sysErrorP - location to store system error
*
* Returns:
* *TCL_OK* if requirements pass, else *TCL_ERROR* with an error. A message
* is stored in the interpreter unless the noexcept attribute is set.
* *TCL_OK* if requirements pass, else *TCL_ERROR* with an error.
*/
CffiResult
CffiCheckPointer(Tcl_Interp *ip,
Expand All @@ -1381,20 +1394,21 @@ CffiCheckPointer(Tcl_Interp *ip,
Tcl_WideInt *sysErrorP)

{
int flags = typeAttrsP->flags;
int flags = typeAttrsP->flags;

if (((flags & CFFI_F_ATTR_ZERO) && pointer != NULL)
|| ((flags & CFFI_F_ATTR_NONZERO) && pointer == NULL)) {
*sysErrorP =
CffiGrabSystemError(typeAttrsP, (Tcl_WideInt)(intptr_t)pointer);
return TCL_ERROR;
}
return TCL_OK;
if (pointer || (flags & CFFI_F_ATTR_NULLOK))
return TCL_OK;
*sysErrorP =
CffiGrabSystemError(typeAttrsP, (Tcl_WideInt)(intptr_t)pointer);
return TCL_ERROR;
}

/* Function: CffiPointerToObj
* Wraps a pointer into a Tcl_Obj based on type settings.
*
* If the pointer is not NULL, it is registered if the type attributes
* indicate it should be wrapped a safe pointer.
*
* Parameters:
* ip - interpreter
* typeAttrsP - descriptor for type and attributes
Expand Down Expand Up @@ -1468,19 +1482,19 @@ CffiPointerFromObj(Tcl_Interp *ip,

CHECK(Tclh_PointerUnwrap(ip, pointerObj, &pv, tagObj));

/*
* Do checks for safe pointers. Note: Cannot use Tclh_PointerObjVerify
* because that rejects NULL pointers.
*/
if (!(typeAttrsP->flags & CFFI_F_ATTR_UNSAFE)) {
/* Only verify registration if not NULL pointer */
if (pv) {
CHECK(Tclh_PointerVerify(ip, pv, tagObj));
if (pv == NULL) {
if ((typeAttrsP->flags & CFFI_F_ATTR_NULLOK) == 0) {
return Tclh_ErrorInvalidValue(ip, NULL, "Pointer is NULL.");
}
}
if (typeAttrsP->flags & CFFI_F_ATTR_NONZERO && pv == NULL) {
Tcl_SetResult(ip, "NULL pointer", TCL_STATIC);
return TCL_ERROR;
else {
/*
* Do checks for safe pointers. Note: Cannot use Tclh_PointerObjVerify
* because that rejects NULL pointers.
*/
if (!(typeAttrsP->flags & CFFI_F_ATTR_UNSAFE)) {
CHECK(Tclh_PointerVerify(ip, pv, tagObj));
}
}

*pointerP = pv;
Expand Down Expand Up @@ -2813,8 +2827,7 @@ CffiResult CffiReturnCleanup(CffiCall *callP)
* Only set if error detected.
*
* Returns:
* *TCL_OK* if requirements pass, else *TCL_ERROR* with an error. A message
* is stored in the interpreter unless the noexcept attribute is set.
* *TCL_OK* if requirements pass, else *TCL_ERROR* with an error.
*/
CffiResult
CffiCheckNumeric(Tcl_Interp *ip,
Expand Down Expand Up @@ -2883,7 +2896,6 @@ CffiCheckNumeric(Tcl_Interp *ip,
return TCL_OK;

failed_requirements:
/* If exceptions are not being reported, do not store error in interp */
*sysErrorP = CffiGrabSystemError(typeAttrsP, value);
return TCL_ERROR;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/common.tcl
Expand Up @@ -45,7 +45,8 @@ namespace eval cffi::test {
variable baseTypes [concat $voidTypes $numericTypes $pointerTypes $stringTypes $charArrayTypes]

variable paramAttrs {in out inout byref}
variable pointerAttrs {unsafe dispose counted}
variable pointerAttrs {nullok unsafe dispose counted}
variable stringAttrs {nullok nullifempty}
variable requirementAttrs {zero nonzero nonnegative positive}
variable errorHandlerAttrs {errno}
if {$::tcl_platform(platform) eq "windows"} {
Expand Down

0 comments on commit a1f0fa5

Please sign in to comment.