[Rcpp-commits] r3910 - in pkg/Rcpp: . inst/include/Rcpp src

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Wed Nov 7 16:45:41 CET 2012


Author: jjallaire
Date: 2012-11-07 16:45:41 +0100 (Wed, 07 Nov 2012)
New Revision: 3910

Modified:
   pkg/Rcpp/ChangeLog
   pkg/Rcpp/inst/include/Rcpp/exceptions.h
   pkg/Rcpp/src/Attributes.cpp
   pkg/Rcpp/src/AttributesParser.cpp
   pkg/Rcpp/src/AttributesParser.h
   pkg/Rcpp/src/exceptions.cpp
Log:
validate exported C++ functions before calling

Modified: pkg/Rcpp/ChangeLog
===================================================================
--- pkg/Rcpp/ChangeLog	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/ChangeLog	2012-11-07 15:45:41 UTC (rev 3910)
@@ -1,3 +1,12 @@
+2012-11-07  JJ Allaire <jj at rstudio.org>
+
+        * src/Attributes.cpp: validate exported C++ functions before calling;
+        use static rather than inline for stubs to avoid call-site bloat
+        * src/AttributesParser.h: add signature and isHidden methods
+        * src/AttributesParser.cpp: add signature and isHidden methods
+        * src/exceptions.cpp: add function_not_exported exception
+        * include/Rcpp/exceptions.h: add function_not_exported exception
+
 2012-11-07  Romain Francois <romain at r-enthusiasts.com>
 
         * src/Language.cpp: Language gains a fast_eval method, without the whole try/catch

Modified: pkg/Rcpp/inst/include/Rcpp/exceptions.h
===================================================================
--- pkg/Rcpp/inst/include/Rcpp/exceptions.h	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/inst/include/Rcpp/exceptions.h	2012-11-07 15:45:41 UTC (rev 3910)
@@ -108,6 +108,7 @@
 RCPP_EXCEPTION_CLASS(binding_not_found, std::string("binding not found: '") + message + "'" )
 RCPP_EXCEPTION_CLASS(binding_is_locked, std::string("binding is locked: '") + message + "'" )
 RCPP_EXCEPTION_CLASS(no_such_namespace, std::string("no such namespace: '") + message + "'" )
+RCPP_EXCEPTION_CLASS(function_not_exported, std::string("function not exported: ") + message)
 RCPP_EXCEPTION_CLASS(eval_error, message )
 
 #undef RCPP_EXCEPTION_CLASS

Modified: pkg/Rcpp/src/Attributes.cpp
===================================================================
--- pkg/Rcpp/src/Attributes.cpp	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/src/Attributes.cpp	2012-11-07 15:45:41 UTC (rev 3910)
@@ -218,8 +218,7 @@
                                     bool verbose) = 0;
         virtual void writeEnd() = 0;
         
-        virtual bool commit(const std::vector<std::string>& includes,
-                            const std::vector<std::string>& prototypes) = 0;
+        virtual bool commit(const std::vector<std::string>& includes) = 0;
         
         // Remove the generated file entirely
         bool remove() {
@@ -237,6 +236,14 @@
         std::ostream& ostr() {
             return codeStream_;
         }
+        
+        // Shared knowledge about the special export validation function
+        std::string exportValidationFunction() {
+            return "validateExported";
+        } 
+        std::string exportValidationFunctionRegisteredName() {
+            return "RcppExports_" + exportValidationFunction();
+        }
     
         // Commit the stream -- is a no-op if the existing code is identical
         // to the generated code. Returns true if data was written and false
@@ -330,6 +337,24 @@
         
             // generate functions
             generateCppModuleFunctions(ostr(), attributes, verbose);
+            
+            // track prototypes/signatures for inclusion in the preamble
+            for (SourceFileAttributes::const_iterator 
+                it = attributes.begin(); it != attributes.end(); ++it) {
+                if (isExportedFunction(*it)) {
+                    
+                    // prototypes
+                    std::ostringstream ostr;
+                    ostr << it->function();
+                    prototypes_.push_back(ostr.str());   
+                    
+                    // signatures
+                    if (!it->function().isHidden()) {
+                        signatures_.push_back(
+                            it->function().signature(exportedName(*it)));
+                    }
+                }
+            }
          
             // verbose if requested
             if (verbose)
@@ -337,31 +362,59 @@
         }
     
         virtual void writeEnd() {
+            
+            // add our export validation helper
+            ostr() << "    Rcpp::function(\"" 
+                   << exportValidationFunctionRegisteredName() << "\", &" 
+                   << exportValidationFunction() << ");" << std::endl;
+
             ostr() << "}" << std::endl;   
         }
         
-        virtual bool commit(const std::vector<std::string>& includes,
-                            const std::vector<std::string>& prototypes) {
+        virtual bool commit(const std::vector<std::string>& includes) {
             
-            // generate preamble 
+            // includes 
             std::ostringstream ostr;
             if (!includes.empty()) {
                 for (std::size_t i=0;i<includes.size(); i++)
                     ostr << includes[i] << std::endl;
-                ostr << std::endl;
             }
+            ostr << "#include <string>" << std::endl;
+            ostr << "#include <set>" << std::endl;
+            ostr << std::endl;
             
-            if (!prototypes.empty()) {
-                for (std::size_t i=0;i<prototypes.size(); i++)
-                    ostr << prototypes[i] << ";" << std::endl;
+            // prototypes
+            if (!prototypes_.empty()) {
+                for (std::size_t i=0;i<prototypes_.size(); i++)
+                    ostr << prototypes_[i] << ";" << std::endl;
                 ostr << std::endl;
             }
             
+            // generate a function that can be used to validate exported
+            // functions and their signatures prior to looking up with
+            // GetCppCallable (otherwise inconsistent signatures between
+            // client and library would cause a crash)
+            ostr << "static bool " << exportValidationFunction() 
+                 << "(const std::string& sig)" << " {" << std::endl;
+            ostr << "    static std::set<std::string> signatures;" 
+                 << std::endl;
+            ostr << "    if (signatures.empty()) {" << std::endl;
+            for (std::size_t i=0;i<signatures_.size(); i++) {
+                ostr << "        signatures.insert(\"" << signatures_[i] 
+                     << "\");" << std::endl;
+            }
+            ostr << "    }" << std::endl;
+            ostr << "    return signatures.find(sig) != signatures.end();"
+                 << std::endl;
+            ostr << "}" << std::endl << std::endl;
+            
             // commit with preamble
-            return ExportsGenerator::commit(ostr.str());
-                                
+            return ExportsGenerator::commit(ostr.str());                  
         }
-    
+        
+    private:
+        std::vector<std::string> prototypes_;
+        std::vector<std::string> signatures_;
     };
     
     // Class which manages generating the header file for the package
@@ -382,8 +435,28 @@
         
         virtual void writeBegin() {
             ostr() << "namespace " << package() << " {" << std::endl;
+            
+            // Write our export validation helper function. Putting it in 
+            // an anonymous namespace will hide it from callers and give
+            // it per-translation unit linkage
+            ostr() << "    namespace {" << std::endl;
+            
+            ostr() << "        void " << exportValidationFunction()  
+                   << "(const std::string& sig) {" << std::endl;
+            std::string ptrName = "p_" + exportValidationFunction();
+            ostr() << "            static bool(" << "*" << ptrName 
+                   << ")(const std::string&) = "
+                   << getCppCallable(exportValidationFunctionRegisteredName())
+                   << ";" << std::endl;
+            ostr() << "            if (!" << ptrName << "(sig))" << std::endl;
+            ostr() << "                throw Rcpp::function_not_exported(\""
+                   << "Function '\" + sig + \"' not found in " << package()
+                   << "\");" << std::endl;
+            ostr() << "        }" << std::endl;
+            
+            ostr() << "    }" << std::endl << std::endl;
         }
-    
+        
         virtual void writeFunctions(const SourceFileAttributes &attributes,
                                     bool verbose) {
                                         
@@ -402,35 +475,28 @@
                     Function function = 
                         it->function().renamedTo(exportedName(*it));
                         
-                    // if the function starts with "." then it's a 
-                    // a hidden R-only function
-                    if (function.name().find_first_of('.') == 0)
+                    // if it's hidden then don't generate a C++ interface
+                    if (function.isHidden())
                         continue;  
                     
-                    ostr() << "    inline " << function << " {" 
+                    ostr() << "    static " << function << " {" 
                             << std::endl;
                     
                     std::string ptrName = "p_" + function.name();
-                    ostr() << "        static " << function.type() 
-                           << "(*" << ptrName << ")(";
-                    
-                    const std::vector<Argument>& args = 
-                                                function.arguments();
-                    
-                    for (std::size_t i = 0; i<args.size(); i++) {
-                        ostr() << args[i].type();
-                        if (i != (args.size()-1))
-                            ostr() << ",";
-                    }
-                    
-                    ostr() << ") = Rcpp::GetCppCallable"
-                           << "(\"" << package() << "\", "
-                           << "\"" << package() << "_RcppExports\", "
-                           << "\"" << function.name() << "\");" 
+                    ostr() << "        static " << function.signature(ptrName);
+                    ostr() << " = NULL;" << std::endl;
+                    ostr() << "        if (" << ptrName << " == NULL) {" 
                            << std::endl;
+                    ostr() << "            " << exportValidationFunction()
+                           << "(\"" << function.signature() << "\");" 
+                           << std::endl;
+                    ostr() << "            " << ptrName << " = " 
+                           << getCppCallable(function.name()) << ";" 
+                           << std::endl;
+                    ostr() << "        }" << std::endl;
+                    ostr() << "        return " << ptrName  << "(";
                     
-                    ostr() << "        return " << ptrName  << "(";
-                           
+                    const std::vector<Argument>& args = function.arguments();
                     for (std::size_t i = 0; i<args.size(); i++) {
                         ostr() << args[i].name();
                         if (i != (args.size()-1))
@@ -447,8 +513,7 @@
             ostr() << "}" << std::endl;
         }
         
-        virtual bool commit(const std::vector<std::string>& includes,
-                            const std::vector<std::string>& prototypes) {
+        virtual bool commit(const std::vector<std::string>& includes) {
             
             if (hasCppInterface_) {
                 
@@ -472,6 +537,17 @@
         }
         
     private:
+        
+        std::string getCppCallable(const std::string& function) const {
+            std::ostringstream ostr;
+            ostr << "Rcpp::GetCppCallable"
+                 << "(\"" << package() << "\", "
+                 << "\"" << package() + "_RcppExports\", "
+                 << "\"" << function << "\")";
+            return ostr.str();
+        }
+        
+    private:
         std::string includeDir_;
         bool hasCppInterface_;
     };
@@ -531,8 +607,7 @@
             }
         }
         
-        virtual bool commit(const std::vector<std::string>& includes,
-                            const std::vector<std::string>& prototypes) {
+        virtual bool commit(const std::vector<std::string>& includes) {
             return ExportsGenerator::commit();                    
         }
     
@@ -578,13 +653,12 @@
         
         // Commit and return a list of the files that were updated
         std::vector<std::string> commit(
-                    const std::vector<std::string>& includes,
-                    const std::vector<std::string>& prototypes) {
+                                const std::vector<std::string>& includes) {
             
             std::vector<std::string> updated;
             
             for(Itr it = generators_.begin(); it != generators_.end(); ++it) {
-                if ((*it)->commit(includes, prototypes))
+                if ((*it)->commit(includes))
                     updated.push_back((*it)->targetFile());
             }
                
@@ -636,12 +710,11 @@
     Rcpp::List platform = Rcpp::as<Rcpp::List>(sPlatform);
     std::string fileSep = Rcpp::as<std::string>(platform["file.sep"]); 
      
-    // initialize generators and namespace/prototype vectors
+    // initialize generators
     ExportsGenerators generators;
     generators.add(new CppExportsGenerator(packageDir, packageName, fileSep));
     generators.add(new RExportsGenerator(packageDir, packageName, fileSep));
     generators.add(new CppIncludeGenerator(packageDir, packageName, fileSep));
-    std::vector<std::string> prototypes;
     
     // write begin
     generators.writeBegin();
@@ -658,12 +731,7 @@
             
         // confirm we have attributes
         haveAttributes = true;
-            
-        // copy prototypes
-        std::copy(attributes.prototypes().begin(),
-                  attributes.prototypes().end(),
-                  std::back_inserter(prototypes));
-        
+               
         // write functions
         generators.writeFunctions(attributes, verbose);
     }
@@ -674,7 +742,7 @@
     // commit or remove
     std::vector<std::string> updated;
     if (haveAttributes)
-        updated = generators.commit(includes, prototypes);  
+        updated = generators.commit(includes);  
     else
         updated = generators.remove();
                                                                                                                    

Modified: pkg/Rcpp/src/AttributesParser.cpp
===================================================================
--- pkg/Rcpp/src/AttributesParser.cpp	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/src/AttributesParser.cpp	2012-11-07 15:45:41 UTC (rev 3910)
@@ -65,6 +65,26 @@
 
 namespace Rcpp {
 namespace attributes_parser {
+    
+    // Generate a type signature for the function with the provided name
+    // (type signature == function pointer declaration)
+    std::string Function::signature(const std::string& name) const {
+        
+        std::ostringstream ostr;
+        
+        ostr << type() << "(*" << name << ")(";
+        
+        const std::vector<Argument>& args = arguments();
+        for (std::size_t i = 0; i<args.size(); i++) {
+            ostr << args[i].type();
+            if (i != (args.size()-1))
+                ostr << ",";
+        }
+        ostr << ")";
+        
+        return ostr.str();
+    }
+    
 
     // Parse attribute parameter from parameter text
     Param::Param(const std::string& paramText)
@@ -293,15 +313,8 @@
              
             // parse the function (unless we are at the end of the file in
             // which case we print a warning)
-            if ((lineNumber + 1) < lines_.size()) {
+            if ((lineNumber + 1) < lines_.size())
                 function = parseFunction(lineNumber + 1);
-                if (!function.empty()) {
-                    std::ostringstream ostr;
-                    ostr << function;
-                    prototypes_.push_back(ostr.str());
-                }
-                
-            }
             else 
                 rcppExportWarning("No function found", lineNumber);    
         }  

Modified: pkg/Rcpp/src/AttributesParser.h
===================================================================
--- pkg/Rcpp/src/AttributesParser.h	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/src/AttributesParser.h	2012-11-07 15:45:41 UTC (rev 3910)
@@ -19,6 +19,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Rcpp.  If not, see <http://www.gnu.org/licenses/>.
 
+
+#ifndef Rcpp__AttributesParser__h
+#define Rcpp__AttributesParser__h
+
 #include <string>
 #include <vector>
 #include <iosfwd>
@@ -85,6 +89,13 @@
             return Function(type(), name, arguments(), source());
         }
         
+        std::string signature() const { return signature(name()); }
+        std::string signature(const std::string& name) const;
+        
+        bool isHidden() const {
+            return name().find_first_of('.') == 0;
+        }
+        
         bool empty() const { return name().empty(); }
         
         const Type& type() const { return type_; }
@@ -199,12 +210,7 @@
             else
                 return false;            
         }
-        
-        // function prototypes
-        const std::vector<std::string>& prototypes() const {
-            return prototypes_;
-        }
-       
+         
     private:
     
         // Parsing helpers
@@ -248,10 +254,10 @@
         std::string sourceFile_;
         CharacterVector lines_;
         std::vector<Attribute> attributes_;
-        std::vector<std::string> prototypes_;
         std::vector<std::string> roxygenBuffer_;
     };
 
 } // namespace attributes_parser
 } // namespace Rcpp
 
+#endif // Rcpp__AttributesParser__h
\ No newline at end of file

Modified: pkg/Rcpp/src/exceptions.cpp
===================================================================
--- pkg/Rcpp/src/exceptions.cpp	2012-11-07 15:07:14 UTC (rev 3909)
+++ pkg/Rcpp/src/exceptions.cpp	2012-11-07 15:45:41 UTC (rev 3910)
@@ -39,6 +39,7 @@
     RCPP_EXCEPTION_WHAT(binding_not_found)
     RCPP_EXCEPTION_WHAT(binding_is_locked)
     RCPP_EXCEPTION_WHAT(no_such_namespace)
+    RCPP_EXCEPTION_WHAT(function_not_exported)
     RCPP_EXCEPTION_WHAT(eval_error)
 
 #undef RCPP_EXCEPTION_WHAT



More information about the Rcpp-commits mailing list