Skip to content

an exception while a module initializes aborts the R session #1525

Open

Description

@kevinushey

The boot function that RCPP_MODULE generates is an extern "C" entry point, called from R via .Call() when the module is loaded (Module(), loadModule(), sourceCpp()), but it has no BEGIN_RCPP/END_RCPP:

#define RCPP_MODULE(name) \
void _rcpp_module_##name##_init() ; \
static Rcpp::Module _rcpp_module_##name( # name ) ; \
extern "C" SEXP _rcpp_module_boot_##name(){ \
::setCurrentScope( & _rcpp_module_##name ) ; \
_rcpp_module_##name##_init( ) ; \
Rcpp::XPtr<Rcpp::Module> mod_xp(& _rcpp_module_##name , false); \
::setCurrentScope( 0 ) ; \
return mod_xp ; \
} \
void _rcpp_module_##name##_init()

#define RCPP_MODULE(name)                                               \
    void _rcpp_module_##name##_init() ;                                 \
    static Rcpp::Module _rcpp_module_##name( # name ) ;                 \
    extern "C" SEXP _rcpp_module_boot_##name(){                         \
        ::setCurrentScope( & _rcpp_module_##name ) ;                    \
        _rcpp_module_##name##_init( ) ;                                 \
        Rcpp::XPtr<Rcpp::Module> mod_xp(& _rcpp_module_##name , false); \
        ::setCurrentScope( 0 ) ;                                        \
        return mod_xp ;                                                 \
    }                                                                   \
    void _rcpp_module_##name##_init()

So any C++ exception thrown while the module body runs escapes into R's C code and std::terminate() aborts the session. The module body is ordinary user code, and Rcpp itself throws from it, e.g. .derives<T>("Name") throws std::range_error("no such class") when Name is not (yet) exposed, and anything that allocates or calls Rcpp::stop() can throw too. The exception also skips ::setCurrentScope( 0 ), leaving the global module scope pointing at the half-initialized module.

This is the same class of problem as #1495 / #1496 (which fixed the .External method entry points), for the module boot function.

Reproducible example

repro.cpp:

#include <Rcpp.h>

using namespace Rcpp;

class Shape {
public:
    virtual ~Shape() {}
    virtual double area() const { return 0.0; }
};

class Square : public Shape {
public:
    Square(double side_) : side(side_) {}
    double area() const { return side * side; }
    double side;
};

RCPP_MODULE(shape_module) {
    class_<Shape>("Shape")
        .method("area", &Shape::area);

    class_<Square>("Square")
        .derives<Shape>("Shpae")  // typo in the parent class name
        .constructor<double>();
}

repro.R:

res <- tryCatch(
    Rcpp::sourceCpp("repro.cpp"),
    error = function(e) conditionMessage(e)
)
cat("result:", res, "\n")

Output with R 4.6.1 (macOS 27, Apple clang 21, Rcpp 1.1.2.6 / current master):

libc++abi: terminating due to uncaught exception of type std::range_error: no such class
Abort trap: 6              Rscript repro.R

Rscript exits with code 134 (SIGABRT); the tryCatch() never gets a chance. In a package, the same typo takes down the session at library() time.

I'll follow up with a PR that wraps the boot function in BEGIN_RCPP/END_RCPP, so the exception becomes an R error, and resets the module scope with an RAII guard so it is cleared on the error path too. The change is confined to the macro, so it takes effect when a package is rebuilt.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions