Safe C++

FAQ

  1. As a contributor of a library to Boost, what do I need to know about Safe C++?

    The current lack of memory-safety makes it too easy for malicious software to exploit C++ language vulnerabilities and perform a variety of attacks. However, retrofitting the C++ language with memory-safe constructs has proven to be daunting. The Safe C++ proposal for a memory-safe set of operations is currently in a state of indefinite hiatus.

    Clearly there could be significant interest in safe versions of Boost libraries, though the level of work involved extends well beyond rewriting a library using safe extensions, as all dependencies would also have to be safe versions too.

    Currently, an astute developer should use known safe practices (some of which are shown below), avoid unsafe libraries if there is a choice, and be aware of the discussions on safe coding practices going on in social media.

  2. What kind of feedback did the proposal for Safe C++ receive?

    Positive feedback centered on appreciation of the initiative to address longstanding safety concerns in C++. More challenging feedback has included concerns about the complexity of integrating new safety features into the existing C++ framework, balancing enhanced safety with the language’s core design features of performance and flexibility, and competition from the RUST and Swift programming languages.

  3. Are there references I can read that will help me understand safe concepts and so understand the online discussions?

    Yes, in addition to the, now stalled, Safe C++ proposal, the C++ safety, in context blog post, by Herb Sutter, has been written for a broad audience. Also by Herb Sutter, there is a paper entitled Core safety Profiles: Specification, adoptability, and impact.

    If you refer to the References section of any of these papers, you will find a range of books, papers, presentations and the like that delve to various depths into safety issues. For example, the Safety Profiles: Type-and-resource Safe programming in ISO Standard C++, by Bjarne Stroustrup and Gabriel Dos Reis, outlines a talk on the broad spectrum of safety issues in a chattier style than the more formal programming papers - and might be a good place to start!

  4. Can you recommend some Boost libraries that demonstrate current best safe-coding practices?

    By examining the source code and documentation for any of these libraries, you should be able to educate yourself on a robust approach to safe programming, using current development tools.

    For memory-safety, Boost.SmartPtr provides smart pointer types like boost::shared_ptr, boost::weak_ptr, and boost::scoped_ptr to manage dynamic memory safely and avoid common pitfalls like memory leaks and dangling pointers. Boost.Pool offers memory pooling utilities that efficient managing of memory allocations while minimizing fragmentation. It can help show how to avoid unsafe manual memory management.

    For type-safety, Boost.StaticAssert facilitates compile-time checks with BOOST_STATIC_ASSERT, ensuring that certain conditions are met during compilation, thus improving type-safety. Also, Boost.TypeTraits supplies a set of tools for type introspection, enabling safer template programming by providing ways to query and manipulate types.

    For resource-safety Boost.Filesystem is designed to work with file paths and directories safely, minimizing errors in handling filesystem resources and ensuring proper cleanup. Boost.ScopeExit provides a mechanism for ensuring cleanup of resources (e.g., releasing locks or closing file handles) when a scope is exited, both normally or due to an exception. And Boost.Interprocess facilitates safe and efficient interprocess communication (IPC), managing shared memory and other resources in a resource-safe way.

    For thread-safety Boost.Thread offers portable thread management and synchronization primitives (such as boost::mutex, boost::lock_guard) to help developers write thread-safe code. Boost.Asio enables asynchronous I/O operations with an emphasis on thread-safety, making it easier to build safe and scalable networked applications. At a lower level, Boost.Atomic provides atomic operations for thread-safe programming, avoiding data races in concurrent applications.

    For a more general approach to safety, Boost.Optional introduces a way to handle optional values safely, avoiding issues like null pointer dereferencing. Boost.Variant2 provides a type-safe union type, ensuring that only one active type is stored at any time, preventing type misuse errors. Boost.Coroutine2 implements stackful coroutines with resource management in mind, preventing unsafe usage patterns.

  5. Using current development tools what are the design principles of safe programming?

    Current best practices start with the use of static and compile-time checks to enforce constraints early. For resource-safety the idiom is Resource Acquisition Is Initialization (RAII). This idiom ties the lifetime of a resource to a programming object, so that when the object is created the resource is initialized, and when the object is destroyed the resource is released. However, the central theme of current safety is Encapsulation - the encapsulation of known unsafe operations in well-tested, robust, reusable abstractions, for example:

    • Instead of exposing raw pointers, use smart pointers or custom encapsulation to ensure safe memory management:

      //
      // Unsafe code
      //
      
      int* allocateArray(size_t size) {
          return new int[size];
      }
      
      void useArray() {
          int* arr = allocateArray(10);
      
          // No bounds checking.
          arr[10] = 42;
      
          // Forgetting to delete could cause memory leaks.
          delete[] arr;
      }
      
      //
      // Safe encapsulation
      //
      
      #include <vector>
      #include <memory>
      
      class SafeArray {
      private:
          std::unique_ptr<int[]> data;
          size_t size;
      
      public:
          SafeArray(size_t size) : data(std::make_unique<int[]>(size)), size(size) {}
      
          int& operator[](size_t index) {
              if (index >= size) {
                  throw std::out_of_range("Index out of range");
              }
              return data[index];
          }
      
          size_t getSize() const { return size; }
      };
      
      void useSafeArray() {
          SafeArray arr(10);
      
          // Safe access
          arr[0] = 42;
          try {
      
              // Throws an exception
              arr[10] = 13;
          } catch (const std::out_of_range& e) {
              std::cerr << e.what() << std::endl;
          }
      }
    • Handle file operations safely by ensuring that the file is properly closed after use.

      //
      // Unsafe code
      //
      
      void writeFile(const std::string& filename) {
          FILE* file = fopen(filename.c_str(), "w");
          if (file) {
              fputs("Hello, World!", file);
      
              // Forgetting fclose could cause resource leaks.
          }
      }
      
      //
      // Safe encapsulation
      //
      
      #include <fstream>
      #include <string>
      
      class FileHandler {
      private:
          std::ofstream file;
      
      public:
          explicit FileHandler(const std::string& filename) {
              file.open(filename, std::ios::out);
              if (!file) {
                  throw std::ios_base::failure("Failed to open file");
              }
          }
      
          ~FileHandler() {
              if (file.is_open()) {
                  file.close();
              }
          }
      
          void write(const std::string& content) {
              if (!file) {
                  throw std::ios_base::failure("File not open");
              }
              file << content;
          }
      };
      
      void safeWriteFile(const std::string& filename) {
          try {
              FileHandler fh(filename);
              fh.write("Hello, World!");
          } catch (const std::exception& e) {
              std::cerr << "Error: " << e.what() << std::endl;
          }
      }
    • Prevent race conditions by wrapping shared resources in a thread-safe interface.

      //
      // Unsafe code
      //
      
      #include <iostream>
      #include <thread>
      #include <vector>
      
      int counter = 0;
      
      void incrementCounter() {
          for (int i = 0; i < 1000; ++i) {
      
              // Race condition
              ++counter;
          }
      }
      
      void unsafeThreads() {
          std::thread t1(incrementCounter);
          std::thread t2(incrementCounter);
          t1.join();
          t2.join();
      
          // Undefined behavior
          std::cout << "Counter: " << counter << std::endl;
      }
      
      //
      // Safe encapsulation
      //
      
      #include <iostream>
      #include <thread>
      #include <vector>
      #include <mutex>
      
      class ThreadSafeCounter {
      private:
          int counter = 0;
          std::mutex mtx;
      
      public:
          void increment() {
              std::lock_guard<std::mutex> lock(mtx);
              ++counter;
          }
      
          int get() const {
              return counter;
          }
      };
      
      void safeThreads() {
          ThreadSafeCounter counter;
      
          auto worker = [&counter]() {
              for (int i = 0; i < 1000; ++i) {
                  counter.increment();
              }
          };
      
          std::thread t1(worker);
          std::thread t2(worker);
          t1.join();
          t2.join();
      
          // Guaranteed correct result
          std::cout << "Counter: " << counter.get() << std::endl;
      }
    • Instead of using raw sockets, encapsulate them in a class that ensures proper resource cleanup.

      //
      // Unsafe code
      //
      
      #include <sys/socket.h>
      #include <unistd.h>
      
      int createSocket() {
          int sock = socket(AF_INET, SOCK_STREAM, 0);
          if (sock == -1) {
              perror("Socket creation failed");
              return -1;
          }
      
          // Forgetting close(sock) could cause resource leaks.
          return sock;
      }
      
      //
      // Safe encapsulation
      //
      
      #include <sys/socket.h>
      #include <unistd.h>
      #include <stdexcept>
      
      class SafeSocket {
      private:
          int sock;
      
      public:
          SafeSocket() {
              sock = socket(AF_INET, SOCK_STREAM, 0);
              if (sock == -1) {
                  throw std::runtime_error("Socket creation failed");
              }
          }
      
          ~SafeSocket() {
              if (sock != -1) {
                  close(sock);
              }
          }
      
          int getSocket() const {
              return sock;
          }
      };

      By wrapping low-level operations in safe abstractions, you make the code easier to use and much harder to misuse!