Skip to content

Commit eff9150

Browse files
committed
validate module objects before converting them to c++ classes
1 parent a6f354e commit eff9150

5 files changed

Lines changed: 156 additions & 4 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/api/meat/as.h (as_module_object_internal): Check
4+
that the argument is a live module object, raising an error for
5+
deserialized, uninitialized and non-module objects instead of handing
6+
out a NULL or garbage address; a new overload taking the expected C++
7+
type also requires the object's class to be that type or one that
8+
derives() from it (#1523)
9+
* inst/include/Rcpp/as.h: Use the new overload when converting to
10+
module classes
11+
* inst/tinytest/cpp/Module.cpp: Add a class deriving from another
12+
* inst/tinytest/test_module.R: Test the new checks
13+
114
2026-10-23 Dirk Eddelbuettel <edd@debian.org>
215

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

‎inst/include/Rcpp/api/meat/as.h‎

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,100 @@
2121
namespace Rcpp{
2222
namespace internal{
2323

24+
// the address of the C++ object held by the module object whose
25+
// environment is env (see cpp_object_initializer() in R/Module.R)
26+
inline void* module_object_address(const Environment& env){
27+
SEXP xp = env.get(".pointer") ;
28+
if( xp == R_NilValue )
29+
throw not_compatible( "Expecting a C++ module object" ) ;
30+
31+
// the dummy object of a class without a default constructor
32+
if( TYPEOF(xp) != EXTPTRSXP )
33+
throw not_initialized() ;
34+
35+
// e.g. a deserialized object
36+
void* ptr = R_ExternalPtrAddr(xp) ;
37+
if( ptr == NULL )
38+
throw ::Rcpp::exception( "external pointer is not valid" ) ;
39+
40+
return ptr ;
41+
}
42+
43+
#ifndef RCPP_NO_MODULES
44+
// whether the module class cl is the C++ class typeinfo_name, or derives
45+
// from it through class_::derives(), which records each parent by its R
46+
// class name, "Rcpp_" followed by its name in the module
47+
inline bool module_class_is( class_Base* cl, Module* module, const char* typeinfo_name ){
48+
if( cl->has_typeinfo_name(typeinfo_name) )
49+
return true ;
50+
51+
std::vector<class_Base*> todo(1, cl) ;
52+
std::set<class_Base*> seen ;
53+
while( module && !todo.empty() ){
54+
class_Base* current = todo.back() ;
55+
todo.pop_back() ;
56+
if( !seen.insert(current).second )
57+
continue ;
58+
59+
if( current->has_typeinfo_name(typeinfo_name) )
60+
return true ;
61+
62+
for( size_t i = 0; i < current->parents.size(); i++ ){
63+
const std::string& parent = current->parents[i] ;
64+
if( parent.compare(0, 5, "Rcpp_") != 0 )
65+
continue ;
66+
std::string name = parent.substr(5) ;
67+
if( module->has_class(name) )
68+
todo.push_back( module->get_class_pointer(name) ) ;
69+
}
70+
}
71+
return false ;
72+
}
73+
#endif
74+
75+
// packages call this one directly, so it stays; it cannot check the class
2476
inline void* as_module_object_internal(SEXP obj){
77+
if( !Rf_isS4(obj) )
78+
throw not_compatible( "Expecting a C++ module object" ) ;
2579
Environment env(obj) ;
26-
SEXP xp = env.get(".pointer") ;
27-
return R_ExternalPtrAddr(xp );
80+
return module_object_address(env) ;
81+
}
82+
83+
inline void* as_module_object_internal(SEXP obj, const char* typeinfo_name){
84+
if( !Rf_isS4(obj) ){
85+
throw not_compatible(
86+
"Expecting an object of C++ class '%s'",
87+
demangle(typeinfo_name)
88+
) ;
89+
}
90+
Environment env(obj) ;
91+
void* ptr = module_object_address(env) ;
92+
93+
#ifndef RCPP_NO_MODULES
94+
SEXP cppclass = env.get(".cppclass") ;
95+
if( TYPEOF(cppclass) != EXTPTRSXP || R_ExternalPtrAddr(cppclass) == NULL ){
96+
throw not_compatible(
97+
"Expecting an object of C++ class '%s'",
98+
demangle(typeinfo_name)
99+
) ;
100+
}
101+
class_Base* cl = reinterpret_cast<class_Base*>( R_ExternalPtrAddr(cppclass) ) ;
102+
103+
SEXP module_xp = env.get(".module") ;
104+
Module* module = NULL ;
105+
if( TYPEOF(module_xp) == EXTPTRSXP )
106+
module = reinterpret_cast<Module*>( R_ExternalPtrAddr(module_xp) ) ;
107+
108+
if( !module_class_is(cl, module, typeinfo_name) ){
109+
throw not_compatible(
110+
"Expecting an object of C++ class '%s', got an object of class '%s'",
111+
demangle(typeinfo_name),
112+
cl->name
113+
) ;
114+
}
115+
#endif
116+
117+
return ptr ;
28118
}
29119

30120
}

‎inst/include/Rcpp/as.h‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,17 @@ namespace Rcpp {
8989
}
9090

9191
void* as_module_object_internal(SEXP obj);
92+
void* as_module_object_internal(SEXP obj, const char* typeinfo_name);
9293

9394
template <typename T> object<T> as_module_object(SEXP x) {
94-
return (T*) as_module_object_internal(x);
95+
return (T*) as_module_object_internal(x, typeid(T).name());
9596
}
9697

9798
/** handling object<T> */
9899
template <typename T> T as(SEXP x, ::Rcpp::traits::r_type_module_object_const_pointer_tag) {
99100
typedef typename Rcpp::traits::remove_const<T>::type T_NON_CONST;
100-
return const_cast<T>((T_NON_CONST)as_module_object_internal(x));
101+
typedef typename Rcpp::traits::un_pointer<T>::type CLASS;
102+
return const_cast<T>((T_NON_CONST)as_module_object_internal(x, typeid(CLASS).name()));
101103
}
102104

103105
template <typename T> T as(SEXP x, ::Rcpp::traits::r_type_module_object_pointer_tag) {

‎inst/tinytest/cpp/Module.cpp‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,24 @@ double Test_get_x_pointer(ModuleTest* x) {
137137
return x->value;
138138
}
139139

140+
class ModuleBase {
141+
public:
142+
ModuleBase() : value(1.0) {}
143+
virtual ~ModuleBase() {}
144+
double value;
145+
};
146+
147+
class ModuleDerived : public ModuleBase {
148+
public:
149+
ModuleDerived() { value = 2.0; }
150+
};
151+
152+
RCPP_EXPOSED_CLASS_NODECL(ModuleBase)
153+
154+
double ModuleBase_value(const ModuleBase& x) {
155+
return x.value;
156+
}
157+
140158
class ModuleGadget {
141159
public:
142160
ModuleGadget() {}
@@ -217,6 +235,17 @@ RCPP_MODULE(demoModule) {
217235
.method("get" , &ModuleRandomizer::get)
218236
;
219237

238+
class_<ModuleBase>("ModuleBase")
239+
.constructor()
240+
;
241+
242+
class_<ModuleDerived>("ModuleDerived")
243+
.derives<ModuleBase>("ModuleBase")
244+
.constructor()
245+
;
246+
247+
function("ModuleBase_value", &ModuleBase_value);
248+
220249
class_<ModuleGadget>("ModuleGadget")
221250
.constructor()
222251
.method("value", static_cast<void (ModuleGadget::*)(int)>(&ModuleGadget::value))

‎inst/tinytest/test_module.R‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ expect_equal( attr_Test_get_x_const_pointer(test), 3.0 )
7575
expect_equal( attr_Test_get_x_ref(test), 3.0 )
7676
expect_equal( attr_Test_get_x_pointer(test), 3.0 )
7777

78+
## module object arguments are validated before their address is handed to
79+
## C++, instead of reinterpreting or dereferencing whatever is there (#1523)
80+
expect_error( Test_get_x_const_ref( new( ModuleWorld ) ),
81+
"Expecting an object of C++ class 'ModuleTest', got an object of class 'ModuleWorld'",
82+
fixed = TRUE )
83+
expect_error( attr_Test_get_x_ref( new( ModuleWorld ) ),
84+
"got an object of class 'ModuleWorld'", fixed = TRUE )
85+
expect_error( Test_get_x_pointer( unserialize( serialize( test, NULL ) ) ),
86+
"external pointer is not valid" )
87+
uninitialized <- new( ModuleTest )
88+
expect_error( Test_get_x_ref( uninitialized ), "not initialized" )
89+
expect_error( Test_get_x_const_pointer( 1 ),
90+
"Expecting an object of C++ class 'ModuleTest'", fixed = TRUE )
91+
92+
## objects of a class that derives() from the expected one are accepted
93+
expect_equal( ModuleBase_value( new( ModuleBase ) ), 1.0 )
94+
expect_equal( ModuleBase_value( new( ModuleDerived ) ), 2.0 )
95+
7896
# test.Module.property <- function(){
7997
w <- new( ModuleNum )
8098
expect_equal( w$x, 0.0 )

0 commit comments

Comments
 (0)