Skip to content

Commit 173cad0

Browse files
committed
turn exceptions during module initialization into r errors
1 parent a6f354e commit 173cad0

5 files changed

Lines changed: 89 additions & 10 deletions

File tree

‎ChangeLog‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
2026-10-29 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot
4+
function in BEGIN_RCPP / END_RCPP so that an exception thrown while the
5+
module initializes raises an R error instead of terminating the session
6+
(#1525)
7+
* inst/include/Rcpp/Module.h (internal::ModuleScope): New RAII guard
8+
clearing the current module scope however the module body exits
9+
* inst/tinytest/cpp/ModuleInitError.cpp: New module whose initialization
10+
throws
11+
* inst/tinytest/cpp/Module.cpp (Module_scope_is_cleared): New
12+
* inst/tinytest/test_module.R: Test loading the failing module
13+
114
2026-10-23 Dirk Eddelbuettel <edd@debian.org>
215

316
* DESCRIPTION (Version, Date): Roll micro version and date

‎inst/include/Rcpp/Module.h‎

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -610,18 +610,37 @@ namespace Rcpp {
610610

611611
}
612612

613+
namespace Rcpp {
614+
namespace internal {
615+
616+
// makes module the target of class_<>, function(), etc. while an
617+
// RCPP_MODULE body runs, and clears it however the body exits
618+
class ModuleScope {
619+
public:
620+
explicit ModuleScope( Module* module ){ ::setCurrentScope( module ) ; }
621+
~ModuleScope(){ ::setCurrentScope( 0 ) ; }
622+
623+
ModuleScope( const ModuleScope& ) = delete ;
624+
ModuleScope& operator=( const ModuleScope& ) = delete ;
625+
} ;
626+
627+
}
628+
}
629+
613630
#define RCPP_MODULE_BOOT(name) _rcpp_module_boot_##name
614631

615-
#define RCPP_MODULE(name) \
616-
void _rcpp_module_##name##_init() ; \
617-
static Rcpp::Module _rcpp_module_##name( # name ) ; \
618-
extern "C" SEXP _rcpp_module_boot_##name(){ \
619-
::setCurrentScope( & _rcpp_module_##name ) ; \
620-
_rcpp_module_##name##_init( ) ; \
621-
Rcpp::XPtr<Rcpp::Module> mod_xp(& _rcpp_module_##name , false); \
622-
::setCurrentScope( 0 ) ; \
623-
return mod_xp ; \
624-
} \
632+
// the boot function is called from R via .Call(), so exceptions thrown by
633+
// the module body must be turned into R errors
634+
#define RCPP_MODULE(name) \
635+
void _rcpp_module_##name##_init() ; \
636+
static Rcpp::Module _rcpp_module_##name( # name ) ; \
637+
extern "C" SEXP _rcpp_module_boot_##name(){ \
638+
BEGIN_RCPP \
639+
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
640+
_rcpp_module_##name##_init( ) ; \
641+
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
642+
END_RCPP \
643+
} \
625644
void _rcpp_module_##name##_init()
626645

627646
// silly little dance to suppress a 'defined but not used variable' warning

‎inst/tinytest/cpp/Module.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,8 @@ double attr_Test_get_x_pointer(ModuleTest* x) {
244244
return x->value;
245245
}
246246

247+
// [[Rcpp::export]]
248+
bool Module_scope_is_cleared() {
249+
return getCurrentScope() == NULL;
250+
}
251+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2+
//
3+
// ModuleInitError.cpp: Rcpp R/C++ interface class library -- module whose
4+
// initialization throws
5+
//
6+
// Copyright (C) 2026 Kevin Ushey
7+
//
8+
// This file is part of Rcpp.
9+
//
10+
// Rcpp is free software: you can redistribute it and/or modify it
11+
// under the terms of the GNU General Public License as published by
12+
// the Free Software Foundation, either version 2 of the License, or
13+
// (at your option) any later version.
14+
//
15+
// Rcpp is distributed in the hope that it will be useful, but
16+
// WITHOUT ANY WARRANTY; without even the implied warranty of
17+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
// GNU General Public License for more details.
19+
//
20+
// You should have received a copy of the GNU General Public License
21+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
22+
23+
#include <Rcpp.h>
24+
25+
using namespace Rcpp;
26+
27+
class ModuleInitError {
28+
public:
29+
ModuleInitError() {}
30+
};
31+
32+
RCPP_MODULE(moduleInitError) {
33+
// the parent was never exposed, so derives() throws std::range_error
34+
class_<ModuleInitError>("ModuleInitError")
35+
.derives<ModuleInitError>("NoSuchParent")
36+
;
37+
}

‎inst/tinytest/test_module.R‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ expect_equal(r$get(10), x10)
108108
r <- new( ModuleRandomizer )
109109
expect_error( r$get(10L), "not initialized" )
110110

111+
## an exception thrown while a module initializes raises an R error rather
112+
## than terminating the R session, and clears the module scope (#1525)
113+
expect_error( Rcpp::sourceCpp("cpp/ModuleInitError.cpp"), "no such class" )
114+
expect_true( Module_scope_is_cleared() )
115+
111116
# test.Module.flexible.semantics <- function( ){
112117
expect_equal( test_reference( seq(0,10) ), 11L )
113118
expect_equal( test_const_reference( seq(0,10) ), 11L )

0 commit comments

Comments
 (0)