[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