├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── API.hpp └── detail │ ├── Exception.hpp │ └── Library.hpp ├── main.cpp └── src ├── API.cpp └── detail ├── Exception.cpp └── Library.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | run 2 | build/* 3 | 4 | # Created by https://www.gitignore.io/api/c++,c,cuda,cmake,linux,windows,osx 5 | 6 | ### C++ ### 7 | # Prerequisites 8 | *.d 9 | 10 | # Compiled Object files 11 | *.slo 12 | *.lo 13 | *.o 14 | *.obj 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Compiled Dynamic libraries 21 | *.so 22 | *.dylib 23 | *.dll 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | *.lib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | 40 | 41 | ### C ### 42 | # Prerequisites 43 | 44 | # Object files 45 | *.ko 46 | *.elf 47 | 48 | # Linker output 49 | *.ilk 50 | *.map 51 | *.exp 52 | 53 | # Precompiled Headers 54 | 55 | # Libraries 56 | 57 | # Shared objects (inc. Windows DLLs) 58 | *.so.* 59 | 60 | # Executables 61 | *.i*86 62 | *.x86_64 63 | *.hex 64 | 65 | # Debug files 66 | *.dSYM/ 67 | *.su 68 | *.idb 69 | *.pdb 70 | 71 | # Kernel Module Compile Results 72 | *.mod* 73 | *.cmd 74 | modules.order 75 | Module.symvers 76 | Mkfile.old 77 | dkms.conf 78 | 79 | 80 | ### CUDA ### 81 | *.i 82 | *.ii 83 | *.gpu 84 | *.ptx 85 | *.cubin 86 | *.fatbin 87 | 88 | 89 | ### CMake ### 90 | CMakeCache.txt 91 | CMakeFiles 92 | CMakeScripts 93 | Makefile 94 | cmake_install.cmake 95 | install_manifest.txt 96 | CTestTestfile.cmake 97 | 98 | 99 | ### Linux ### 100 | *~ 101 | 102 | # temporary files which can be created if a process still has a handle open of a deleted file 103 | .fuse_hidden* 104 | 105 | # KDE directory preferences 106 | .directory 107 | 108 | # Linux trash folder which might appear on any partition or disk 109 | .Trash-* 110 | 111 | # .nfs files are created when an open file is removed but is still being accessed 112 | .nfs* 113 | 114 | 115 | ### Windows ### 116 | # Windows image file caches 117 | Thumbs.db 118 | ehthumbs.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | ### OSX ### 137 | *.DS_Store 138 | .AppleDouble 139 | .LSOverride 140 | 141 | # Icon must end with two \r 142 | Icon 143 | # Thumbnails 144 | ._* 145 | # Files that might appear in the root of a volume 146 | .DocumentRevisions-V100 147 | .fseventsd 148 | .Spotlight-V100 149 | .TemporaryItems 150 | .Trashes 151 | .VolumeIcon.icns 152 | .com.apple.timemachine.donotpresent 153 | # Directories potentially created on remote AFP share 154 | .AppleDB 155 | .AppleDesktop 156 | Network Trash Folder 157 | Temporary Items 158 | .apdisk -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | matrix: 4 | include: 5 | - os: osx 6 | osx_image: xcode11.2 7 | - os: osx 8 | osx_image: xcode10.3 9 | 10 | - os: linux 11 | dist: bionic 12 | - os: linux 13 | dist: xenial 14 | 15 | - os: windows 16 | 17 | 18 | install: 19 | - | 20 | cd ${TRAVIS_BUILD_DIR} 21 | mkdir -p build 22 | cd build 23 | - cmake .. 24 | - cmake --build . -j 4 25 | 26 | script: 27 | - ./../run -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######### CMake Version ################################### 2 | cmake_minimum_required(VERSION 2.8.11) 3 | ########################################################### 4 | 5 | 6 | ######### Info ############################################ 7 | message( STATUS ">> CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}" ) 8 | message( STATUS ">> CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}" ) 9 | ########################################################### 10 | 11 | 12 | ######### Project Name #################################### 13 | project( exceptions ) 14 | set( LIBRARY_NAME mylib ) 15 | set( EXECUTABLE_NAME run ) 16 | ########################################################### 17 | 18 | 19 | ######### Have the binary placed into the source head ##### 20 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} ) 21 | ### Output paths for multi-config builds (e.g. msvc) 22 | foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) 23 | string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) 24 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_SOURCE_DIR} ) 25 | endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) 26 | ########################################################### 27 | 28 | 29 | ######### Prevent messing up the source tree ############## 30 | set( CMAKE_DISABLE_SOURCE_CHANGES ON ) 31 | set( CMAKE_DISABLE_IN_SOURCE_BUILD ON ) 32 | ########################################################### 33 | 34 | 35 | ######### Library ######################################### 36 | set(MYLIB_HEADER_FILES 37 | include/API.hpp 38 | include/detail/Library.hpp 39 | include/detail/Exception.hpp 40 | ) 41 | 42 | set(MYLIB_SOURCE_FILES 43 | src/API.cpp 44 | src/detail/Library.cpp 45 | src/detail/Exception.cpp 46 | ) 47 | 48 | add_library( ${LIBRARY_NAME} ${MYLIB_SOURCE_FILES} ) 49 | 50 | set_target_properties( ${LIBRARY_NAME} PROPERTIES 51 | CXX_STANDARD 11 52 | CXX_STANDARD_REQUIRED ON 53 | CXX_EXTENSIONS OFF ) 54 | 55 | target_include_directories( ${LIBRARY_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include) 56 | target_include_directories( ${LIBRARY_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/thirdparty) 57 | 58 | target_compile_options( ${LIBRARY_NAME} PRIVATE 59 | $<$,$,$>: 60 | -Werror -Wall -Wextra -Wpedantic -pedantic-errors -Wconversion -Wsign-conversion > 61 | $<$: 62 | /WX /W4 >) 63 | ########################################################### 64 | 65 | 66 | ######### Executable ###################################### 67 | add_executable( ${EXECUTABLE_NAME} main.cpp ) 68 | 69 | set_target_properties( ${EXECUTABLE_NAME} PROPERTIES 70 | CXX_STANDARD 11 71 | CXX_STANDARD_REQUIRED ON 72 | CXX_EXTENSIONS OFF ) 73 | 74 | target_link_libraries( ${EXECUTABLE_NAME} PUBLIC mylib) 75 | ########################################################### -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Gideon Müller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Proper, portable exception handling with backtracing in C++11 2 | ==================================================================== 3 | 4 | [![Build Status](https://api.travis-ci.org/GPMueller/mwe-cpp-exception.svg?branch=master)](https://travis-ci.org/GPMueller/mwe-cpp-exception) 5 | 6 | **See [GPMueller/trace](https://github.com/GPMueller/trace) for a proper exception backtracing library.** 7 | 8 | This MWE shows how [`std::nested_exception`](http://en.cppreference.com/w/cpp/error/nested_exception) and [`std::throw_with_nested`](http://en.cppreference.com/w/cpp/error/throw_with_nested) can be applied in order to not lose information while propagating 9 | an original `std::exception` upwards through a chain of function calls and create a **backtrace** without any overhead (compare e.g. logging of debug messages). 10 | This avoids much of the need for any debugging and provides a way of ensuring that a library does not crash ungracefully. 11 | Output should look something like this: 12 | ``` 13 | Library API: Exception caught in function 'api_function' 14 | Backtrace: 15 | ~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed 16 | ~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt" 17 | ``` 18 | 19 | The example may seem a bit overkill, displaying a library with an API layer, but it shows a thorough way of dealing cleanly with exceptions. 20 | `main` calls an API function, which in turn calls a library function which deliberately throws. The API function catches the exception and 21 | calls a handler function. 22 | 23 | Inspiration for this MWE was taken from https://stackoverflow.com/a/37227893/4069571 and https://stackoverflow.com/a/348862/4069571 24 | 25 | Build 26 | -------------------------------------------------------------------- 27 | 28 | CMake is used to configure the build. To build the executable: 29 | ``` 30 | mkdir -p build 31 | cd build 32 | cmake .. 33 | cmake --build . 34 | ``` 35 | 36 | TODO 37 | -------------------------------------------------------------------- 38 | - Extend this MWE with example exceptions which do not require the code to terminate. 39 | - Create unit tests with catch, using e.g. `REQUIRE_THROWS` 40 | -------------------------------------------------------------------------------- /include/API.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace mylib 4 | { 5 | enum class API_RETURN 6 | { 7 | SUCCESS, 8 | FAILURE 9 | }; 10 | 11 | API_RETURN function() noexcept; 12 | } -------------------------------------------------------------------------------- /include/detail/Exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mylib 7 | { 8 | namespace detail 9 | { 10 | namespace exception 11 | { 12 | 13 | // Custom exception class to be used for more practical throwing 14 | class Exception : public std::runtime_error 15 | { 16 | public: 17 | Exception(const std::string & message, const char * file, unsigned int line): 18 | std::runtime_error(message) 19 | { 20 | _message = std::string(file) + ":" + std::to_string(line) + " : " + message; 21 | } 22 | 23 | ~Exception() throw() {} 24 | 25 | const char * what() const throw() 26 | { 27 | return _message.c_str(); 28 | } 29 | 30 | private: 31 | std::string _message; 32 | }; 33 | 34 | // Rethrow (creates a std::nested_exception) an exception, using the Exception class 35 | // which contains file and line info. The original exception is preserved... 36 | void rethrow(const std::string & message, const char * file, unsigned int line); 37 | 38 | // General Exception handler 39 | void Handle_Exception(const std::exception & ex, const std::string & function =""); 40 | 41 | } 42 | 43 | // Shorthand for throwing an Exception with file and line info using macros 44 | #define library_throw(message) throw exception::Exception(message, __FILE__, __LINE__); 45 | 46 | // Shorthand for rethrowing and Exception with file and line info using macros 47 | #define library_rethrow(message) exception::rethrow(message, __FILE__, __LINE__); 48 | 49 | // Shorthand for handling an exception, including a backtrace 50 | #define library_handle_exception(ex) exception::Handle_Exception(ex, __func__); 51 | 52 | } 53 | } -------------------------------------------------------------------------------- /include/detail/Library.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace mylib 4 | { 5 | namespace detail 6 | { 7 | void library_function(); 8 | } 9 | } -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | auto value = mylib::function(); 8 | 9 | if( value == mylib::API_RETURN::SUCCESS ) 10 | { 11 | std::cerr << "Library call returned API_SUCCESS\n"; 12 | // In this MWE, we expect api_function to fail and therefore to 13 | // return API_FAILURE (normally it would be the other way around) 14 | std::exit(EXIT_FAILURE); 15 | } 16 | else 17 | std::cerr << "Library call returned API_FAILURE\n"; 18 | } -------------------------------------------------------------------------------- /src/API.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace mylib 6 | { 7 | API_RETURN function() noexcept 8 | try 9 | { 10 | detail::library_function(); 11 | return API_RETURN::SUCCESS; 12 | } 13 | catch( const std::exception & ex ) 14 | { 15 | detail::library_handle_exception(ex); 16 | return API_RETURN::FAILURE; 17 | } 18 | } -------------------------------------------------------------------------------- /src/detail/Exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace mylib 7 | { 8 | namespace detail 9 | { 10 | namespace exception 11 | { 12 | // Rethrow (creates a std::nested_exception) an exception, using the Exception class 13 | // which contains file and line info. The original exception is preserved... 14 | void rethrow(const std::string & message, const char * file, unsigned int line) 15 | try 16 | { 17 | std::rethrow_exception(std::current_exception()); 18 | } 19 | catch (...) 20 | { 21 | std::throw_with_nested(Exception(message, file, line)); 22 | } 23 | 24 | // Backtrace an exception by recursively unwrapping the nested exceptions 25 | void Backtrace(const std::exception & ex) 26 | try 27 | { 28 | std::cerr << ex.what() << std::endl; 29 | rethrow_if_nested(ex); 30 | } 31 | catch( const std::exception & nested_ex ) 32 | { 33 | Backtrace(nested_ex); 34 | } 35 | 36 | // General Exception handler 37 | void Handle_Exception(const std::exception & ex, const std::string & function) 38 | try 39 | { 40 | if (function != "") 41 | std::cerr << "Library API: Exception caught in function \'" << function << "\'" << std::endl; 42 | std::cerr << "Backtrace:" << std::endl; 43 | Backtrace(ex); 44 | } 45 | catch( ... ) 46 | { 47 | std::cerr << "Something went super-wrong! TERMINATING!" << std::endl; 48 | std::exit( EXIT_FAILURE ); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/detail/Library.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace mylib 5 | { 6 | namespace detail 7 | { 8 | void library_function() 9 | try 10 | { 11 | // some erronous code which throws an exception... 12 | // let's pretend we weren't able to open a file 13 | std::string filename = "nonexistent.txt"; 14 | library_throw("could not open file \"" + filename + "\""); 15 | } 16 | catch( const std::exception & ) 17 | { 18 | library_rethrow("library_function failed") 19 | } 20 | } 21 | } --------------------------------------------------------------------------------