Search code examples
rr-package

Registering native routines: why null-terminate arrays?


Writing R Extensions says to null-terminate external interface arrays. Examples and CRAN packages (dplyr, stringi + comment, data.table) all include {NULL, NULL, 0}.

The following foo package passes R CMD check, no notes.

bar = \(x) .Call(c_bar, x)
#include <Rinternals.h>
int bar(SEXP baz) {return TYPEOF(baz);}
static const R_CallMethodDef entries[] = {
    {"c_bar", (DL_FUNC) &bar, 1} // No {NULL, NULL, 0}
};

void R_init_foo(DllInfo* dll) {
    R_registerRoutines(dll, NULL, entries, NULL, NULL);
    R_useDynamicSymbols(dll, FALSE);
}

Question: Why is {NULL, NULL, 0} required / recommended?


Solution

  • The source code of R_registerRoutines in src/main/Rdynload.c (link) is helpful:

        if(callRoutines) {
            for(num = 0; callRoutines[num].name != NULL; num++) {;}
            info->CallSymbols =
                (Rf_DotCallSymbol*)calloc((size_t) num, sizeof(Rf_DotCallSymbol));
            info->numCallSymbols = num;
            for(i = 0; i < num; i++)
                R_addCallRoutine(info, callRoutines+i, info->CallSymbols + i);
        }
    

    Here, callRoutines is a pointer to the first element of an array of R_CallMethodDef structs. By not terminating the array with a "null struct", you are relying on undefined behaviour in the second loop iteration at callRoutines[num].name != NULL, where the array is indexed out of bounds.