From 38a4763ad889bb15ed9816c5d3f55a7c4f18e5ee Mon Sep 17 00:00:00 2001
From: Giovanni Bussi <giovanni.bussi@gmail.com>
Date: Tue, 25 Sep 2018 16:40:09 +0200
Subject: [PATCH] Allow remapping exceptions.

When using the C++ interface, this change remaps exception within plumed
to a class that inherits from std::runtime_error.
This allows them to be catched also from an MD code linked to
a different C++ library.

I also removed some cpp options, I think it is better to enforce
exceptions to be managed in this way.
---
 regtest/basic/rt-make-exceptions/main.cpp |   8 +-
 src/core/PlumedMain.cpp                   |  13 +-
 src/core/PlumedMain.h                     |  21 +++
 src/core/PlumedMainInitializer.cpp        |  11 +-
 src/wrapper/Plumed.h                      | 150 +++++++++++++++-------
 5 files changed, 149 insertions(+), 54 deletions(-)

diff --git a/regtest/basic/rt-make-exceptions/main.cpp b/regtest/basic/rt-make-exceptions/main.cpp
index 7d6b65397..238b435e9 100644
--- a/regtest/basic/rt-make-exceptions/main.cpp
+++ b/regtest/basic/rt-make-exceptions/main.cpp
@@ -1,5 +1,4 @@
 #include "plumed/tools/Stopwatch.h"
-#include "plumed/tools/Exception.h"
 #include "plumed/wrapper/Plumed.h"
 #include <fstream>
 #include <iostream>
@@ -13,7 +12,7 @@ void test_line(std::ostream & ofs,Plumed & p,const std::string & arg){
   try{
     p.cmd(cmd.c_str(),arg.c_str());
     ofs<<"+++ !!!! uncatched !!!!"<<std::endl;
-  } catch(Exception&e) {
+  } catch(Plumed::Exception&e) {
     ofs<<"+++ catched"<<std::endl;
   }
 }
@@ -25,7 +24,7 @@ void test_this(std::ostream & ofs,Plumed & p,const std::string & cmd,const void*
   try{
     p.cmd(cmd.c_str(),arg);
     ofs<<"+++ !!!! uncatched !!!!"<<std::endl;
-  } catch(Exception&e) {
+  } catch(Plumed::Exception&e) {
     ofs<<"+++ catched"<<std::endl;
   }
 }
@@ -41,7 +40,8 @@ int main(){
     try{
       sw.pause();
       ofs<<"+++ !!!! uncatched !!!!"<<std::endl;
-    } catch(Exception& e) {
+// this is not of type PLMD::Plumed::Exception since it is thrown within the library
+    } catch(std::exception & e) {
       ofs<<"+++ catched"<<std::endl;
     }
   }
diff --git a/src/core/PlumedMain.cpp b/src/core/PlumedMain.cpp
index 69983877f..4bc8f69b6 100644
--- a/src/core/PlumedMain.cpp
+++ b/src/core/PlumedMain.cpp
@@ -258,6 +258,17 @@ void PlumedMain::cmd(const std::string & word,void*val) {
         if( nw==2 ) mydatafetcher->setData( words[1], "", val );
         else mydatafetcher->setData( words[1], words[2], val );
         break;
+      /* ADDED WITH API==6 */
+      case cmd_setErrorHandler:
+      {
+        /* the layout of this structure should not be changed since it is also defined in Plumed.h */
+        typedef struct {
+          void(*error_handler)(const char*);
+        } plumed_error_handler;
+        if(val) error_handler=static_cast<plumed_error_handler*>(val)->error_handler;
+        else error_handler=nullptr;
+      }
+      break;
       case cmd_read:
         CHECK_INIT(initialized,word);
         if(val)readInputFile(static_cast<char*>(val));
@@ -471,7 +482,7 @@ void PlumedMain::cmd(const std::string & word,void*val) {
       }
     }
 
-  } catch (Exception &e) {
+  } catch (std::exception &e) {
     if(log.isOpen()) {
       log<<"\n\n################################################################################\n\n";
       log<<e.what();
diff --git a/src/core/PlumedMain.h b/src/core/PlumedMain.h
index d3f350a90..5c6616ccc 100644
--- a/src/core/PlumedMain.h
+++ b/src/core/PlumedMain.h
@@ -94,6 +94,14 @@ public:
   Communicator&multi_sim_comm=*multi_sim_comm_fwd;
 
 private:
+/// Error handler.
+/// Pointer to a function that is called an exception thrown within
+/// the library is about to leave the library.
+/// Can be used to remap exceptions in case the plumed wrapper was compiled
+/// with a different version of the C++ standard library.
+/// Should only be called from \ref plumed_plumedmain_cmd().
+  void (*error_handler)(const char*) = nullptr;
+
 /// Forward declaration.
   ForwardDecl<DLLoader> dlloader_fwd;
   DLLoader& dlloader=*dlloader_fwd;
@@ -376,6 +384,10 @@ public:
   bool updateFlagsTop();
 /// Set end of input file
   void setEndPlumed();
+/// Call error handler.
+/// Should only be called from \ref plumed_plumedmain_cmd().
+/// If the error handler was not set, returns false.
+  bool callErrorHandler(const char* msg)const;
 };
 
 /////
@@ -446,6 +458,15 @@ void PlumedMain::setEndPlumed() {
   endPlumed=true;
 }
 
+inline
+bool PlumedMain::callErrorHandler(const char* msg)const {
+  if(error_handler) {
+    error_handler(msg);
+    return true;
+  } else return false;
+}
+
+
 }
 
 #endif
diff --git a/src/core/PlumedMainInitializer.cpp b/src/core/PlumedMainInitializer.cpp
index 15eaccc0c..4cc5b0b13 100644
--- a/src/core/PlumedMainInitializer.cpp
+++ b/src/core/PlumedMainInitializer.cpp
@@ -36,7 +36,16 @@ extern "C" void*plumed_plumedmain_create() {
 
 extern "C" void plumed_plumedmain_cmd(void*plumed,const char*key,const void*val) {
   plumed_massert(plumed,"trying to use a plumed object which is not initialized");
-  static_cast<PLMD::PlumedMain*>(plumed)->cmd(key,val);
+  auto p=static_cast<PLMD::PlumedMain*>(plumed);
+  try {
+    p->cmd(key,val);
+  } catch(std::exception & e) {
+// we are at library boundaries.
+// if a error_handler was provided, we use it to manage this exception.
+// this allows an exception to be catched also if the MD code
+// was linked against a different C++ library
+    if(!p->callErrorHandler(e.what())) throw;
+  }
 }
 
 extern "C" void plumed_plumedmain_finalize(void*plumed) {
diff --git a/src/wrapper/Plumed.h b/src/wrapper/Plumed.h
index 737dd754a..09a59243c 100644
--- a/src/wrapper/Plumed.h
+++ b/src/wrapper/Plumed.h
@@ -330,13 +330,6 @@
   - Functions to create a plumed object referencing to another one, implementing a reference counter
     (\ref plumed_create_reference(), \ref plumed_create_reference_v(), \ref plumed_create_reference_f().
 
-  These functions were not available in PLUMED <=2.4. As a consequence, if you use one of those functions
-  you will loose binary compatibility with libplumed.so (or libplumed.dylib on OSX).
-  This is going to happen also if you use the C++ operators based on these functions, such as
-  the new conversion operators from C and FORTRAN.
-  Notice that in any case you will retain compatibility at the level of runtime loading.
-  That is, you will always be able to load an older kernel setting the environment variable `PLUMED_KERNEL`.
-
 */
 
 /* BEGINNING OF DECLARATIONS */
@@ -419,33 +412,6 @@
 #define __PLUMED_WRAPPER_CXX_ANONYMOUS_NAMESPACE 0
 #endif
 
-/*
-  1: using cmd on an invalid object throws an exception
-  0: using cmd on an invalid object exits (default)
-
-  It is set to zero by default for backward compatibility.
-  Notice that setting it to 1 the C++ interface will not be compatible with
-  PLUMED < 2.5 library since it will require plumed_valid().
-  The default value might change in the future.
-
-  Only used in declarations.
-*/
-
-#ifndef __PLUMED_WRAPPER_CXX_THROWS
-#define __PLUMED_WRAPPER_CXX_THROWS 0
-#endif
-
-/*
-  If __PLUMED_WRAPPER_CXX_THROWS==1, use this command to throw an exception when acting on invalid objects.
-  By default, throws a std::runtime_error.
-
-  Only used in declarations.
-*/
-
-#ifndef __PLUMED_WRAPPER_CXX_THROW_CMD
-#define __PLUMED_WRAPPER_CXX_THROW_CMD(x) throw ::std::runtime_error(x)
-#endif
-
 /*
   1: make PLMD::Plumed class polymorphic (default)
   0: make PLMD::Plumed class non-polymorphic
@@ -690,6 +656,18 @@ __PLUMED_WRAPPER_C_BEGIN
 plumed plumed_create_invalid();
 __PLUMED_WRAPPER_C_END
 
+/** \relates plumed
+    \brief Set an error handler
+
+    The error handler is a callback function that will be used in two cases:
+    - If using \ref plumed_cmd on an invalid object
+    - If using a kernel library >=2.5, to handle plumed exception.
+*/
+
+__PLUMED_WRAPPER_C_BEGIN
+void plumed_set_error_handler(plumed p,void (*error_handler)(const char*));
+__PLUMED_WRAPPER_C_END
+
 /** \relates plumed
     \brief Tells p to execute a command.
 
@@ -939,10 +917,10 @@ __PLUMED_WRAPPER_EXTERN_C_END /*}*/
 #else
 #include <stdlib.h>
 #endif
-#if __PLUMED_WRAPPER_CXX_THROWS
+
+/* these are to include standard exceptions */
 #include <exception>
 #include <stdexcept>
-#endif
 
 /* C++ interface is hidden in PLMD namespace (same as plumed library) */
 namespace PLMD {
@@ -965,7 +943,45 @@ class Plumed {
     C structure.
   */
   plumed main;
+
+  /**
+    Error handler installed to rethrow exceptions.
+  */
+  static void cxx_error_handler(const char*what) {
+    throw Plumed::Exception(what);
+  }
+
+  /**
+    Install error handler.
+
+    Called with no argument it installs a C++ handler that allows rethrowing exceptions.
+
+    Should be used anytime a handler is created (constructor)
+  */
+  Plumed& set_error_handler(void(*error_handler)(const char*)=NULL) {
+    if(!error_handler) error_handler=cxx_error_handler;
+    try {
+      plumed_set_error_handler(main,error_handler);
+    } catch(...) {
+      /* Don't do anything. This is just to avoid troubles when loading kernels
+         between 2.4 and 2.5, that declare api=6 but do not implement setErrorHandler */
+    }
+    return *this;
+  }
+
 public:
+
+  /**
+    Class used to rethrow PLUMED exceptions.
+  */
+
+  class Exception :
+    public ::std::runtime_error
+  {
+  public:
+    Exception(const char* msg): ::std::runtime_error(msg) {}
+  };
+
   /**
      Check if plumed is installed (for runtime binding)
      \return true if plumed is installed, false otherwise
@@ -1043,9 +1059,6 @@ public:
      \note Equivalent to plumed_gcmd()
   */
   static void gcmd(const char* key,const void* val=NULL) {
-#if __PLUMED_WRAPPER_CXX_THROWS
-    if(!plumed_gvalid()) __PLUMED_WRAPPER_CXX_THROW_CMD("accessing an invalid global object, check your PLUMED_KERNEL variable.");
-#endif
     plumed_gcmd(key,val);
   }
   /**
@@ -1078,6 +1091,7 @@ public:
 Plumed()__PLUMED_WRAPPER_CXX_NOEXCEPT :
   main(plumed_create())
   {
+    set_error_handler();
   }
 
   /**
@@ -1195,7 +1209,7 @@ Plumed(Plumed&&p)__PLUMED_WRAPPER_CXX_NOEXCEPT :
   */
   static Plumed dlopen(const char* path)__PLUMED_WRAPPER_CXX_NOEXCEPT  {
 // use decref to remove the extra reference
-    return Plumed(plumed_create_dlopen(path)).decref();
+    return Plumed(plumed_create_dlopen(path)).decref().set_error_handler();
   }
 
   /**
@@ -1205,7 +1219,7 @@ Plumed(Plumed&&p)__PLUMED_WRAPPER_CXX_NOEXCEPT :
   */
   static Plumed dlopen(const char* path,int mode)__PLUMED_WRAPPER_CXX_NOEXCEPT  {
 // use decref to remove the extra reference
-    return Plumed(plumed_create_dlopen2(path,mode)).decref();
+    return Plumed(plumed_create_dlopen2(path,mode)).decref().set_error_handler();
   }
   /** Invalid constructor. Available as of PLUMED 2.5.
 
@@ -1236,7 +1250,7 @@ Plumed(Plumed&&p)__PLUMED_WRAPPER_CXX_NOEXCEPT :
   */
   static Plumed invalid() __PLUMED_WRAPPER_CXX_NOEXCEPT  {
 // use decref to remove the extra reference
-    return Plumed(plumed_create_invalid()).decref();
+    return Plumed(plumed_create_invalid()).decref().set_error_handler();
   }
 
   /**
@@ -1322,9 +1336,6 @@ Plumed(Plumed&&p)__PLUMED_WRAPPER_CXX_NOEXCEPT :
       \note Equivalent to plumed_cmd()
   */
   void cmd(const char*key,const void*val=NULL) {
-#if __PLUMED_WRAPPER_CXX_THROWS
-    if(!plumed_valid(main)) __PLUMED_WRAPPER_CXX_THROW_CMD("accessing an invalid plumed object");
-#endif
     plumed_cmd(main,key,val);
   }
   /**
@@ -1864,12 +1875,16 @@ typedef struct {
      the last plumed object has been destroyed */
   /* in addition, when creating multiple plumed objects, it is more efficient to keep the library loaded */
   int dlclose;
+  /* 1 if path to kernel was taken from PLUMED_KERNEL var, 0 otherwise */
+  int used_plumed_kernel;
   /* function pointers */
   plumed_plumedmain_function_holder functions;
   /* pointer to the symbol table. NULL if kernel <=2.4 */
   plumed_symbol_table_type* table;
   /* pointer to plumed object */
   void* p;
+  /* error handler */
+  void (*error_handler)(const char*);
 } plumed_implementation;
 
 __PLUMED_WRAPPER_INTERNALS_BEGIN
@@ -1888,11 +1903,13 @@ plumed_implementation* plumed_malloc_pimpl() {
 #endif
   pimpl->dlhandle=NULL;
   pimpl->dlclose=0;
+  pimpl->used_plumed_kernel=0;
   pimpl->functions.create=NULL;
   pimpl->functions.cmd=NULL;
   pimpl->functions.finalize=NULL;
   pimpl->table=NULL;
   pimpl->p=NULL;
+  pimpl->error_handler=NULL;
   return pimpl;
 }
 __PLUMED_WRAPPER_INTERNALS_END
@@ -1920,6 +1937,10 @@ plumed plumed_create(void) {
   pimpl=plumed_malloc_pimpl();
   /* store pointers in pimpl */
   plumed_retrieve_functions(&pimpl->functions,&pimpl->table,&pimpl->dlhandle);
+#if __PLUMED_WRAPPER_LINK_RUNTIME
+  /* note if PLUMED_KERNEL variable was used */
+  pimpl->used_plumed_kernel=1;
+#endif
   /* note if handle should not be dlclosed */
   /* Notice that PLUMED_LOAD_DLCLOSE only affects the kernel linked from PLUMED_KERNEL
      and is not used in plumed_create_dlopen().
@@ -2025,6 +2046,31 @@ plumed plumed_create_invalid() {
 }
 __PLUMED_WRAPPER_C_END
 
+typedef struct {
+  void(*error_handler)(const char*);
+} plumed_error_handler;
+
+__PLUMED_WRAPPER_C_BEGIN
+void plumed_set_error_handler(plumed p,void (*error_handler)(const char*)) {
+  plumed_implementation* pimpl;
+  int api;
+  plumed_error_handler h;
+  /* obtain pimpl */
+  pimpl=(plumed_implementation*) p.p;
+  assert(plumed_check_pimpl(pimpl));
+  pimpl->error_handler=error_handler;
+  /* if plumed object is valid and recent enough, inject error_handler to rethrow exceptions */
+  if(pimpl->p) {
+    api=0;
+    plumed_cmd(p,"getApiVersion",&api);
+    if(api>=6) {
+      h.error_handler=error_handler;
+      plumed_cmd(p,"setErrorHandler",&h);
+    }
+  }
+}
+__PLUMED_WRAPPER_C_END
+
 __PLUMED_WRAPPER_C_BEGIN
 void plumed_cmd(plumed p,const char*key,const void*val) {
   plumed_implementation* pimpl;
@@ -2032,9 +2078,17 @@ void plumed_cmd(plumed p,const char*key,const void*val) {
   pimpl=(plumed_implementation*) p.p;
   assert(plumed_check_pimpl(pimpl));
   if(!pimpl->p) {
-    __PLUMED_FPRINTF(stderr,"+++ ERROR: you are trying to use plumed, but it is not available +++\n");
-    __PLUMED_FPRINTF(stderr,"+++ Check your PLUMED_KERNEL environment variable +++\n");
-    __PLUMED_WRAPPER_STD exit(1);
+    if(pimpl->error_handler) {
+      if(pimpl->used_plumed_kernel) {
+        pimpl->error_handler("You are trying to use plumed, but it is not available.\nCheck your PLUMED_KERNEL environment variable.");
+      } else {
+        pimpl->error_handler("You are trying to use plumed, but it is not available.");
+      }
+    } else {
+      __PLUMED_FPRINTF(stderr,"+++ ERROR: You are trying to use plumed, but it is not available. +++\n");
+      if(pimpl->used_plumed_kernel) __PLUMED_FPRINTF(stderr,"+++ Check your PLUMED_KERNEL environment variable. +++\n");
+      __PLUMED_WRAPPER_STD exit(1);
+    }
   }
   assert(pimpl->functions.create);
   assert(pimpl->functions.cmd);
-- 
GitLab