THRIFT-727 - C++: what() method of generated exceptions will provide more information

Patch: Anna Dymek <aadymek@gmail.com>
Client: C++

This closes #469
diff --git a/compiler/cpp/src/generate/t_cpp_generator.cc b/compiler/cpp/src/generate/t_cpp_generator.cc
index 3edd7c8..426434f 100644
--- a/compiler/cpp/src/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/generate/t_cpp_generator.cc
@@ -138,6 +138,7 @@
   void generate_struct_result_writer(std::ofstream& out, t_struct* tstruct, bool pointers = false);
   void generate_struct_swap(std::ofstream& out, t_struct* tstruct);
   void generate_struct_ostream_operator(std::ofstream& out, t_struct* tstruct);
+  void generate_exception_what_method(std::ofstream& out, t_struct* tstruct);
 
   /**
    * Service-level generation functions
@@ -239,6 +240,9 @@
                                    bool include_values);
 
   void generate_struct_ostream_operator_decl(std::ofstream& f, t_struct* tstruct);
+  void generate_exception_what_method_decl(std::ofstream& f,
+                                           t_struct* tstruct,
+                                           bool external = false);
 
   // These handles checking gen_dense_ and checking for duplicates.
   void generate_local_reflection(std::ofstream& out, t_type* ttype, bool is_definition);
@@ -767,6 +771,9 @@
     generate_move_assignment_operator(f_types_impl_, tstruct);
   }
   generate_struct_ostream_operator(f_types_impl_, tstruct);
+  if (is_exception) {
+    generate_exception_what_method(f_types_impl_, tstruct);
+  }
 }
 
 void t_cpp_generator::generate_copy_constructor(ofstream& out,
@@ -1113,7 +1120,15 @@
   // ostream operator<<
   out << indent() << "friend ";
   generate_struct_ostream_operator_decl(out, tstruct);
-  out << ";" << endl;
+  out << ";" << endl << endl;
+
+  // std::exception::what()
+  if (is_exception) {
+    out << indent() << "mutable std::string thriftTExceptionMessageHolder_;" << endl;
+    out << indent();
+    generate_exception_what_method_decl(out, tstruct, false);
+    out << ";" << endl;
+  }
 
   indent_down();
   indent(out) << "};" << endl << endl;
@@ -1626,6 +1641,16 @@
   out << "std::ostream& operator<<(std::ostream& out, const " << tstruct->get_name() << "& obj)";
 }
 
+void t_cpp_generator::generate_exception_what_method_decl(std::ofstream& out,
+                                                          t_struct* tstruct,
+                                                          bool external) {
+  out << "const char* ";
+  if (external) {
+    out << tstruct->get_name() << "::";
+  }
+  out << "what() const throw()";
+}
+
 namespace struct_ostream_operator_generator {
 void generate_required_field_value(std::ofstream& out, const t_field* field) {
   out << " << to_string(obj." << field->get_name() << ")";
@@ -1700,6 +1725,37 @@
 }
 
 /**
+ * Generates what() method for exceptions
+ */
+void t_cpp_generator::generate_exception_what_method(std::ofstream& out, t_struct* tstruct) {
+  out << indent();
+  generate_exception_what_method_decl(out, tstruct, true);
+  out << " {" << endl;
+
+  indent_up();
+  out << indent() << "try {" << endl;
+
+  indent_up();
+  out << indent() << "std::stringstream ss;" << endl;
+  out << indent() << "ss << \"TException - service has thrown: \" << *this;" << endl;
+  out << indent() << "this->thriftTExceptionMessageHolder_ = ss.str();" << endl;
+  out << indent() << "return this->thriftTExceptionMessageHolder_.c_str();" << endl;
+  indent_down();
+
+  out << indent() << "} catch (const std::exception& e) {" << endl;
+
+  indent_up();
+  out << indent() << "return \"TException - service has thrown: " << tstruct->get_name() << "\";"
+      << endl;
+  indent_down();
+
+  out << indent() << "}" << endl;
+
+  indent_down();
+  out << "}" << endl << endl;
+}
+
+/**
  * Generates a thrift service. In C++, this comprises an entirely separate
  * header and source file. The header file defines the methods and includes
  * the data types defined in the main header file, and the implementation
diff --git a/lib/cpp/test/processor/ProcessorTest.cpp b/lib/cpp/test/processor/ProcessorTest.cpp
index 655f481..40d926e 100644
--- a/lib/cpp/test/processor/ProcessorTest.cpp
+++ b/lib/cpp/test/processor/ProcessorTest.cpp
@@ -765,6 +765,9 @@
     BOOST_FAIL("expected MyError to be thrown");
   } catch (const MyError& e) {
     BOOST_CHECK_EQUAL(message, e.message);
+    // Check if std::exception::what() is handled properly
+    size_t message_pos = std::string(e.what()).find("TException - service has thrown: MyError");
+    BOOST_CHECK_NE(message_pos, std::string::npos);
   }
 
   // Now we should see the events for a normal call finish
diff --git a/tutorial/c_glib/c_glib_server.c b/tutorial/c_glib/c_glib_server.c
index a20ab54..000d97c 100644
--- a/tutorial/c_glib/c_glib_server.c
+++ b/tutorial/c_glib/c_glib_server.c
@@ -231,7 +231,7 @@
          setting the corresponding output parameter to a new instance
          of its type and returning FALSE. */
       *ouch = g_object_new (TYPE_INVALID_OPERATION,
-                            "what", op,
+                            "whatOp", op,
                             "why",  g_strdup ("Cannot divide by 0"),
                             NULL);
       result = FALSE;
@@ -249,7 +249,7 @@
 
   default:
     *ouch = g_object_new (TYPE_INVALID_OPERATION,
-                          "what", op,
+                          "whatOp", op,
                           "why",  g_strdup ("Invalid Operation"),
                           NULL);
     result = FALSE;
diff --git a/tutorial/cpp/CppClient.cpp b/tutorial/cpp/CppClient.cpp
index 258cbd6..2763fee 100644
--- a/tutorial/cpp/CppClient.cpp
+++ b/tutorial/cpp/CppClient.cpp
@@ -58,6 +58,7 @@
     } catch (InvalidOperation& io) {
       cout << "InvalidOperation: " << io.why << endl;
       // or using generated operator<<: cout << io << endl;
+      // or by using std::exception native method what(): cout << io.what() << endl;
     }
 
     work.op = Operation::SUBTRACT;
diff --git a/tutorial/cpp/CppServer.cpp b/tutorial/cpp/CppServer.cpp
index 84b79d4..6b22193 100644
--- a/tutorial/cpp/CppServer.cpp
+++ b/tutorial/cpp/CppServer.cpp
@@ -71,7 +71,7 @@
     case Operation::DIVIDE:
       if (work.num2 == 0) {
         InvalidOperation io;
-        io.what = work.op;
+        io.whatOp = work.op;
         io.why = "Cannot divide by 0";
         throw io;
       }
@@ -79,7 +79,7 @@
       break;
     default:
       InvalidOperation io;
-      io.what = work.op;
+      io.whatOp = work.op;
       io.why = "Invalid Operation";
       throw io;
     }
diff --git a/tutorial/d/server.d b/tutorial/d/server.d
index d32b937..cbcedcc 100644
--- a/tutorial/d/server.d
+++ b/tutorial/d/server.d
@@ -62,7 +62,7 @@
     case Operation.DIVIDE:
       if (work.num2 == 0) {
         auto io = new InvalidOperation();
-        io.what = work.op;
+        io.whatOp = work.op;
         io.why = "Cannot divide by 0";
         throw io;
       }
@@ -70,7 +70,7 @@
       break;
     default:
       auto io = new InvalidOperation();
-      io.what = work.op;
+      io.whatOp = work.op;
       io.why = "Invalid Operation";
       throw io;
     }
diff --git a/tutorial/erl/server.erl b/tutorial/erl/server.erl
index 4915606..f1e7357 100644
--- a/tutorial/erl/server.erl
+++ b/tutorial/erl/server.erl
@@ -44,12 +44,12 @@
         ?tutorial_Operation_MULTIPLY -> Num1 * Num2;
 
         ?tutorial_Operation_DIVIDE when Num2 == 0 ->
-          throw(#invalidOperation{what=Op, why="Cannot divide by 0"});
+          throw(#invalidOperation{whatOp=Op, why="Cannot divide by 0"});
         ?tutorial_Operation_DIVIDE ->
           Num1 div Num2;
 
         _Else ->
-          throw(#invalidOperation{what=Op, why="Invalid operation"})
+          throw(#invalidOperation{whatOp=Op, why="Invalid operation"})
     end.
 
 getStruct(Key) ->
diff --git a/tutorial/haxe/src/CalculatorHandler.hx b/tutorial/haxe/src/CalculatorHandler.hx
index 3376fb6..e9752db 100644
--- a/tutorial/haxe/src/CalculatorHandler.hx
+++ b/tutorial/haxe/src/CalculatorHandler.hx
@@ -67,7 +67,7 @@
                 if (work.num2 == 0)
                 {
                     var io = new InvalidOperation();
-                    io.what = work.op;
+                    io.whatOp = work.op;
                     io.why = "Cannot divide by 0";
                     throw io;
                 }
@@ -75,7 +75,7 @@
 
             default:
                 var io = new InvalidOperation();
-                io.what = work.op;
+                io.whatOp = work.op;
                 io.why = "Unknown operation";
                 throw io;
         }
diff --git a/tutorial/hs/HaskellServer.hs b/tutorial/hs/HaskellServer.hs
index 77f1679..cfe1344 100644
--- a/tutorial/hs/HaskellServer.hs
+++ b/tutorial/hs/HaskellServer.hs
@@ -74,7 +74,7 @@
                     if num2 work == 0 then
                         throw $
                               InvalidOperation {
-                                 invalidOperation_what = fromIntegral $ fromEnum $ op work,
+                                 invalidOperation_whatOp = fromIntegral $ fromEnum $ op work,
                                  invalidOperation_why = "Cannot divide by 0"
                                             }
                     else
diff --git a/tutorial/java/src/CalculatorHandler.java b/tutorial/java/src/CalculatorHandler.java
index 4216be5..92944b0 100644
--- a/tutorial/java/src/CalculatorHandler.java
+++ b/tutorial/java/src/CalculatorHandler.java
@@ -58,7 +58,7 @@
     case DIVIDE:
       if (work.num2 == 0) {
         InvalidOperation io = new InvalidOperation();
-        io.what = work.op.getValue();
+        io.whatOp = work.op.getValue();
         io.why = "Cannot divide by 0";
         throw io;
       }
@@ -66,7 +66,7 @@
       break;
     default:
       InvalidOperation io = new InvalidOperation();
-      io.what = work.op.getValue();
+      io.whatOp = work.op.getValue();
       io.why = "Unknown operation";
       throw io;
     }
diff --git a/tutorial/nodejs/NodeServer.js b/tutorial/nodejs/NodeServer.js
index ba36105..55d3817 100644
--- a/tutorial/nodejs/NodeServer.js
+++ b/tutorial/nodejs/NodeServer.js
@@ -48,7 +48,7 @@
     } else if (work.op === ttypes.Operation.DIVIDE) {
       if (work.num2 === 0) {
         var x = new ttypes.InvalidOperation();
-        x.what = work.op;
+        x.whatOp = work.op;
         x.why = 'Cannot divide by 0';
         result(x);
         return;
@@ -56,7 +56,7 @@
       val = work.num1 / work.num2;
     } else {
       var x = new ttypes.InvalidOperation();
-      x.what = work.op;
+      x.whatOp = work.op;
       x.why = 'Invalid operation';
       result(x);
       return;
diff --git a/tutorial/nodejs/NodeServerPromise.js b/tutorial/nodejs/NodeServerPromise.js
index 4fd0a35..bff287b 100644
--- a/tutorial/nodejs/NodeServerPromise.js
+++ b/tutorial/nodejs/NodeServerPromise.js
@@ -47,14 +47,14 @@
     } else if (work.op === ttypes.Operation.DIVIDE) {
       if (work.num2 === 0) {
         var x = new ttypes.InvalidOperation();
-        x.what = work.op;
+        x.whatOp = work.op;
         x.why = 'Cannot divide by 0';
 		throw x;
       }
       val = work.num1 / work.num2;
     } else {
       var x = new ttypes.InvalidOperation();
-      x.what = work.op;
+      x.whatOp = work.op;
       x.why = 'Invalid operation';
       throw x;
     }
diff --git a/tutorial/ocaml/CalcServer.ml b/tutorial/ocaml/CalcServer.ml
index 24d7d03..b5facb7 100755
--- a/tutorial/ocaml/CalcServer.ml
+++ b/tutorial/ocaml/CalcServer.ml
@@ -49,7 +49,7 @@
 	| Operation.DIVIDE ->
 	    if w#grab_num2 = Int32.zero then
 	      let io = new invalidOperation in
-		io#set_what (Operation.to_i w#grab_op) ;
+		io#set_whatOp (Operation.to_i w#grab_op) ;
 		io#set_why "Cannot divide by 0" ;
 		raise (InvalidOperation io)
 	    else
diff --git a/tutorial/perl/PerlServer.pl b/tutorial/perl/PerlServer.pl
index a40ec69..adec978 100644
--- a/tutorial/perl/PerlServer.pl
+++ b/tutorial/perl/PerlServer.pl
@@ -68,14 +68,14 @@
     if ($num2 == 0)
     {
       my $x = new tutorial::InvalidOperation;
-      $x->what($op);
+      $x->whatOp($op);
       $x->why('Cannot divide by 0');
       die $x;
     }
     $val = $num1 / $num2;
   } else {
     my $x = new tutorial::InvalidOperation;
-    $x->what($op);
+    $x->whatOp($op);
     $x->why('Invalid operation');
     die $x;
   }
diff --git a/tutorial/php/PhpServer.php b/tutorial/php/PhpServer.php
index 4af70a4..22ae43e 100755
--- a/tutorial/php/PhpServer.php
+++ b/tutorial/php/PhpServer.php
@@ -79,7 +79,7 @@
       case \tutorial\Operation::DIVIDE:
         if ($w->num2 == 0) {
           $io = new \tutorial\InvalidOperation();
-          $io->what = $w->op;
+          $io->whatOp = $w->op;
           $io->why = "Cannot divide by 0";
           throw $io;
         }
@@ -87,7 +87,7 @@
         break;
       default:
         $io = new \tutorial\InvalidOperation();
-        $io->what = $w->op;
+        $io->whatOp = $w->op;
         $io->why = "Invalid Operation";
         throw $io;
     }
diff --git a/tutorial/py.tornado/PythonServer.py b/tutorial/py.tornado/PythonServer.py
index 52932ff..7a34107 100755
--- a/tutorial/py.tornado/PythonServer.py
+++ b/tutorial/py.tornado/PythonServer.py
@@ -62,13 +62,13 @@
         elif work.op == Operation.DIVIDE:
             if work.num2 == 0:
                 x = InvalidOperation()
-                x.what = work.op
+                x.whatOp = work.op
                 x.why = "Cannot divide by 0"
                 raise x
             val = work.num1 / work.num2
         else:
             x = InvalidOperation()
-            x.what = work.op
+            x.whatOp = work.op
             x.why = "Invalid operation"
             raise x
 
diff --git a/tutorial/py.twisted/PythonServer.py b/tutorial/py.twisted/PythonServer.py
index f023cac..227f6d4 100755
--- a/tutorial/py.twisted/PythonServer.py
+++ b/tutorial/py.twisted/PythonServer.py
@@ -59,13 +59,13 @@
     elif work.op == Operation.DIVIDE:
       if work.num2 == 0:
         x = InvalidOperation()
-        x.what = work.op
+        x.whatOp = work.op
         x.why = 'Cannot divide by 0'
         raise x
       val = work.num1 / work.num2
     else:
       x = InvalidOperation()
-      x.what = work.op
+      x.whatOp = work.op
       x.why = 'Invalid operation'
       raise x
 
diff --git a/tutorial/py/PythonServer.py b/tutorial/py/PythonServer.py
index 014a12e..533b0ea 100755
--- a/tutorial/py/PythonServer.py
+++ b/tutorial/py/PythonServer.py
@@ -56,13 +56,13 @@
     elif work.op == Operation.DIVIDE:
       if work.num2 == 0:
         x = InvalidOperation()
-        x.what = work.op
+        x.whatOp = work.op
         x.why = 'Cannot divide by 0'
         raise x
       val = work.num1 / work.num2
     else:
       x = InvalidOperation()
-      x.what = work.op
+      x.whatOp = work.op
       x.why = 'Invalid operation'
       raise x
 
diff --git a/tutorial/rb/RubyServer.rb b/tutorial/rb/RubyServer.rb
index 22cec31..1d707d7 100755
--- a/tutorial/rb/RubyServer.rb
+++ b/tutorial/rb/RubyServer.rb
@@ -52,14 +52,14 @@
     elsif work.op == Operation::DIVIDE
       if work.num2 == 0
         x = InvalidOperation.new()
-        x.what = work.op
+        x.whatOp = work.op
         x.why = "Cannot divide by 0"
         raise x
       end
       val = work.num1 / work.num2
     else
       x = InvalidOperation.new()
-      x.what = work.op
+      x.whatOp = work.op
       x.why = "Invalid operation"
       raise x
     end
diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift
index c8710d4..571ab8e 100644
--- a/tutorial/tutorial.thrift
+++ b/tutorial/tutorial.thrift
@@ -113,7 +113,7 @@
  * Structs can also be exceptions, if they are nasty.
  */
 exception InvalidOperation {
-  1: i32 what,
+  1: i32 whatOp,
   2: string why
 }