Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
2026-10-29 Kevin Ushey <kevinushey@gmail.com>

* inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot
function in BEGIN_RCPP / END_RCPP so that an exception thrown while the
module initializes raises an R error instead of terminating the session
(#1525)
* inst/include/Rcpp/Module.h (internal::ModuleScope): New RAII guard
clearing the current module scope however the module body exits
* inst/tinytest/cpp/ModuleInitError.cpp: New module whose initialization
throws
* inst/tinytest/cpp/Module.cpp (Module_scope_is_cleared): New
* inst/tinytest/test_module.R: Test loading the failing module

2026-10-23 Dirk Eddelbuettel <edd@debian.org>

* DESCRIPTION (Version, Date): Roll micro version and date
Expand Down
39 changes: 29 additions & 10 deletions inst/include/Rcpp/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,18 +610,37 @@ namespace Rcpp {

}

namespace Rcpp {
namespace internal {

// makes module the target of class_<>, function(), etc. while an
// RCPP_MODULE body runs, and clears it however the body exits
class ModuleScope {
public:
explicit ModuleScope( Module* module ){ ::setCurrentScope( module ) ; }
~ModuleScope(){ ::setCurrentScope( 0 ) ; }

ModuleScope( const ModuleScope& ) = delete ;
ModuleScope& operator=( const ModuleScope& ) = delete ;
} ;

}
}

#define RCPP_MODULE_BOOT(name) _rcpp_module_boot_##name

#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 ; \
} \
// the boot function is called from R via .Call(), so exceptions thrown by
// the module body must be turned into R errors
#define RCPP_MODULE(name) \
void _rcpp_module_##name##_init() ; \
static Rcpp::Module _rcpp_module_##name( # name ) ; \
extern "C" SEXP _rcpp_module_boot_##name(){ \
BEGIN_RCPP \
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
_rcpp_module_##name##_init( ) ; \
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
END_RCPP \
} \
void _rcpp_module_##name##_init()

// silly little dance to suppress a 'defined but not used variable' warning
Expand Down
5 changes: 5 additions & 0 deletions inst/tinytest/cpp/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,8 @@ double attr_Test_get_x_pointer(ModuleTest* x) {
return x->value;
}

// [[Rcpp::export]]
bool Module_scope_is_cleared() {
return getCurrentScope() == NULL;
}

37 changes: 37 additions & 0 deletions inst/tinytest/cpp/ModuleInitError.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*-
//
// ModuleInitError.cpp: Rcpp R/C++ interface class library -- module whose
// initialization throws
//
// Copyright (C) 2026 Kevin Ushey
//
// This file is part of Rcpp.
//
// Rcpp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// Rcpp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.

#include <Rcpp.h>

using namespace Rcpp;

class ModuleInitError {
public:
ModuleInitError() {}
};

RCPP_MODULE(moduleInitError) {
// the parent was never exposed, so derives() throws std::range_error
class_<ModuleInitError>("ModuleInitError")
.derives<ModuleInitError>("NoSuchParent")
;
}
5 changes: 5 additions & 0 deletions inst/tinytest/test_module.R
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ expect_equal(r$get(10), x10)
r <- new( ModuleRandomizer )
expect_error( r$get(10L), "not initialized" )

## an exception thrown while a module initializes raises an R error rather
## than terminating the R session, and clears the module scope (#1525)
expect_error( Rcpp::sourceCpp("cpp/ModuleInitError.cpp"), "no such class" )
expect_true( Module_scope_is_cleared() )

# test.Module.flexible.semantics <- function( ){
expect_equal( test_reference( seq(0,10) ), 11L )
expect_equal( test_const_reference( seq(0,10) ), 11L )
Expand Down
Loading