Skip to content

All the Safeties

"Safety is a hot topic. What do we mean by 'safety'? How does it relate to correctness? What about security?"

Overview

Sean Parent's "All the Safeties" keynote (C++ on Sea 2023, CppNow 2023) provides a rigorous taxonomy of safety concepts in programming. The talk clarifies the often-conflated terms of safety, correctness, and security, and examines how these concepts apply to C++ specifically.

This is particularly relevant given the industry focus on memory safety and government recommendations about programming language choice.

Defining Terms

Safety vs Correctness

Safety and correctness are often confused but are distinct:

  • Safety: Nothing bad happens (no undefined behavior, no crashes)
  • Correctness: The right thing happens (meets specification)
cpp
// Safe but incorrect
int divide(int a, int b) {
    if (b == 0) return 0;  // Safe (no crash), but wrong answer
    return a / b;
}

// Correct but unsafe (in C++)
int divide(int a, int b) {
    return a / b;  // Correct when b != 0, undefined behavior otherwise
}

// Both safe and correct
std::optional<int> divide(int a, int b) {
    if (b == 0) return std::nullopt;  // Safe
    return a / b;  // Correct
}

Lamport's Definitions

Sean Parent references Leslie Lamport's formal definitions:

  • Safety property: Nothing bad ever happens
  • Liveness property: Something good eventually happens

A program must satisfy both to be correct.

The Safety Taxonomy

Memory Safety

Prevents invalid memory operations:

ViolationDescriptionExample
Buffer overflowAccess beyond boundsarr[n] where n >= size
Use-after-freeAccess freed memorydelete p; *p = 1;
Double freeFree same memory twicedelete p; delete p;
Null dereferenceAccess through null pointernullptr->method()
Uninitialized readRead before writeint x; return x;
cpp
// Memory unsafe code
void danger() {
    int* p = new int(42);
    delete p;
    *p = 10;  // Use-after-free!
}

// Memory safe alternative
void safe() {
    auto p = std::make_unique<int>(42);
    // p automatically managed
}

Type Safety

Ensures values are used according to their type:

ViolationDescriptionExample
Type confusionWrong type interpretationCasting int* to float*
Union misuseReading wrong union memberActive member mismatch
Aliasing violationIncompatible pointer accessStrict aliasing rules
cpp
// Type unsafe
void unsafe() {
    int i = 42;
    float* f = reinterpret_cast<float*>(&i);
    *f = 3.14f;  // Type punning, undefined behavior
}

// Type safe
void safe() {
    int i = 42;
    float f;
    std::memcpy(&f, &i, sizeof(f));  // Defined behavior
}

Thread Safety

Prevents data races and synchronization errors:

ViolationDescriptionExample
Data raceConcurrent unsynchronized accessTwo threads writing same variable
Race conditionTiming-dependent bugCheck-then-act patterns
DeadlockCircular wait for locksLock ordering violation
cpp
// Thread unsafe
int counter = 0;
void increment() { ++counter; }  // Data race if called from multiple threads

// Thread safe
std::atomic<int> counter{0};
void increment() { ++counter; }  // Atomic operation

Resource Safety

Ensures proper resource management:

ViolationDescriptionExample
Resource leakFailure to releaseMemory, file handles, sockets
Use-after-closeUse released resourceFile operations after close
Double releaseRelease same resource twiceDouble close
cpp
// Resource unsafe
void unsafe() {
    FILE* f = fopen("file.txt", "r");
    if (error_condition) return;  // Leak!
    fclose(f);
}

// Resource safe (RAII)
void safe() {
    std::ifstream f("file.txt");
    if (error_condition) return;  // Automatically closed
}

Exception Safety

Guarantees about state when exceptions are thrown:

LevelGuarantee
No-throwNever throws exceptions
StrongIf exception thrown, state unchanged
BasicInvariants maintained, no leaks
NoneNo guarantees
cpp
// Strong exception safety (copy-and-swap)
class Container {
public:
    Container& operator=(Container other) {  // Copy made
        swap(*this, other);                   // No-throw swap
        return *this;                         // Old data destroyed
    }
};

The Security Dimension

Security differs from safety:

  • Safety: Protection from accidental misuse
  • Security: Protection from intentional attack

Memory safety issues often become security vulnerabilities:

Safety IssueSecurity Exploit
Buffer overflowCode injection, ROP attacks
Use-after-freeArbitrary code execution
Integer overflowBuffer size miscalculation
Format stringInformation disclosure, code execution

Safety in C++

Why C++ Is "Unsafe"

C++ provides:

  • Direct memory access
  • Pointer arithmetic
  • Manual memory management
  • Type casting
  • Undefined behavior by design

This enables performance but creates safety risks.

Making C++ Safer

1. Modern C++ Features

cpp
// Use smart pointers
std::unique_ptr<int> p = std::make_unique<int>(42);

// Use containers
std::vector<int> v;
v.at(i);  // Bounds-checked access

// Use string_view instead of char*
void process(std::string_view s);

// Use span for arrays
void process(std::span<int> data);

2. Static Analysis

Tools that find safety issues at compile time:

  • Clang-Tidy
  • PVS-Studio
  • Coverity
  • SonarQube

3. Runtime Sanitizers

Runtime detection of safety violations:

  • AddressSanitizer (ASan) — Memory errors
  • UndefinedBehaviorSanitizer (UBSan) — UB detection
  • ThreadSanitizer (TSan) — Data races
  • MemorySanitizer (MSan) — Uninitialized reads
bash
# Compile with sanitizers
clang++ -fsanitize=address,undefined -g program.cpp

4. Safe Coding Guidelines

Guidelines for Safe Code

1. Use RAII Everywhere

cpp
// BAD: Manual resource management
void bad() {
    int* p = new int(42);
    // ... might throw or return early ...
    delete p;
}

// GOOD: RAII
void good() {
    auto p = std::make_unique<int>(42);
    // Automatically cleaned up
}

2. Prefer Value Semantics

cpp
// BAD: Pointer relationships
class Node {
    Node* parent_;
    std::vector<Node*> children_;
};

// GOOD: Value/index relationships
class Tree {
    struct Node {
        size_t parent;
        std::vector<size_t> children;
    };
    std::vector<Node> nodes_;
};

3. Make Illegal States Unrepresentable

cpp
// BAD: Can be in invalid state
class Connection {
    Socket socket_;
    bool connected_ = false;
public:
    void send(Data d) {
        if (!connected_) throw ...;  // Runtime check
        socket_.send(d);
    }
};

// GOOD: Type system enforces validity
class DisconnectedConnection { /* ... */ };
class ConnectedConnection {
    Socket socket_;
public:
    void send(Data d) { socket_.send(d); }  // Always valid
};

4. Use const Liberally

cpp
// Const prevents accidental modification
void process(const std::vector<int>& data) {
    // Can't accidentally modify data
}

// Const member functions
class Widget {
public:
    int getValue() const { return value_; }  // Can't modify state
};

5. Validate at Boundaries

cpp
// Validate input at API boundaries
void processUserInput(std::string_view input) {
    if (input.size() > MAX_SIZE) {
        throw std::invalid_argument("Input too large");
    }
    if (!isValidFormat(input)) {
        throw std::invalid_argument("Invalid format");
    }
    // Now safe to process
    processValidated(input);
}

The Industry Context

Government Recommendations

Recent reports (NSA, CISA, etc.) recommend:

  • Using memory-safe languages where possible
  • Applying static analysis and sanitizers
  • Following secure coding guidelines

Statistics

Sean Parent cites statistics showing:

  • ~70% of Microsoft's CVEs are memory safety issues
  • Similar percentages at Google, Apple, Mozilla
  • Memory safety is the dominant source of vulnerabilities

The Path Forward

Options for improving C++ safety:

  1. Profiles — Subsets of C++ with safety guarantees
  2. Static analysis — Better tooling
  3. Runtime checks — Sanitizers, contracts
  4. Language evolution — Safer defaults, better abstractions
  5. Interop with safe languages — Rust, etc.

Summary Table

Safety TypeWhat It PreventsC++ Tools
MemoryBuffer overflow, use-after-freeSmart pointers, containers, ASan
TypeType confusion, aliasingstd::variant, std::any, concepts
ThreadData races, deadlocksstd::atomic, std::mutex, TSan
ResourceLeaks, double-freeRAII, smart pointers
ExceptionInconsistent stateStrong guarantee patterns

References

Primary Sources


"Safety is not optional. It's not a feature. It's a requirement." — Sean Parent