Project 12: Bug Catalog - “It’s Not a Bug, It’s a Language Feature”

Build a comprehensive test suite demonstrating every bug and surprising behavior from Chapter 2 of Expert C Programming, turning language quirks into deep understanding.


Quick Reference

Attribute Value
Language C
Difficulty Level 3 (Advanced)
Time 1 Week
Book Reference Expert C Programming, Chapter 2
Coolness 4/5 - Bug Hunter’s Playground
Portfolio Value High - Demonstrates deep language knowledge

1. Learning Objectives

By completing this project, you will:

  1. Catalog C’s historical quirks: Understand why C has behaviors that seem like bugs but are actually intentional design decisions

  2. Master precedence pitfalls: Recognize expressions where operator precedence causes surprising results

  3. Identify implicit conversion traps: Catch silent type conversions that change program behavior

  4. Understand default behaviors: Know what happens when you rely on C defaults (like function return types)

  5. Debug real-world code: Apply this knowledge to find and fix actual bugs

  6. Write defensive code: Learn patterns that avoid common pitfalls

  7. Explain language design decisions: Understand why C was designed this way and defend (or critique) those decisions

  8. Ace C interview questions: Handle trick questions about C behavior with confidence


2. Theoretical Foundation

2.1 Core Concepts

Why Study “Language Features” (Bugs)?

Chapter 2 of “Expert C Programming” catalogs surprising C behaviors with the ironic title “It’s Not a Bug, It’s a Language Feature.” Van der Linden explains that understanding these quirks deeply transforms them from sources of bugs into powerful knowledge:

THE PARADOX OF C QUIRKS
═══════════════════════════════════════════════════════════════════════════

     BEGINNER PERSPECTIVE:         EXPERT PERSPECTIVE:
     ────────────────────          ────────────────────
     "C is full of bugs"           "C reveals its implementation"
     "This shouldn't compile"      "This follows the rules exactly"
     "Who designed this?"          "This is an optimization opportunity"
     "I'll avoid these features"   "I'll leverage these behaviors"

     The difference is UNDERSTANDING, not avoidance.

     EXAMPLE: Signed/unsigned comparison

     Beginner: "This is a bug! -1 should be less than 1!"
         if (-1 < 1u) { /* never executes */ }

     Expert: "The unsigned cast promotes -1 to UINT_MAX. I'll write:"
         if ((int)1u > -1) { /* explicit intent */ }
         // or
         int x = -1; unsigned y = 1;
         if (x < 0 || (unsigned)x < y) { /* safe comparison */ }

2.2 Categories of “Language Features”

Van der Linden organizes C’s quirks into several categories:

THE BUG TAXONOMY
═══════════════════════════════════════════════════════════════════════════

┌─────────────────────────────────────────────────────────────────────────┐
│                     CATEGORY 1: PRECEDENCE PITFALLS                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Operators that bind differently than expected:                         │
│                                                                         │
│  • Bitwise vs logical:  if (flags & FLAG == 0)  // WRONG!               │
│    Parsed as:           if (flags & (FLAG == 0)) // not what you want   │
│                                                                         │
│  • Assignment in conditions: if (c = getchar() != EOF)                  │
│    Parsed as:                if (c = (getchar() != EOF)) // c is 0 or 1 │
│                                                                         │
│  • Shift with addition:  x = y << 4 + z                                 │
│    Parsed as:            x = y << (4 + z)  // not (y << 4) + z          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                     CATEGORY 2: TYPE CONVERSION TRAPS                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Silent conversions that change behavior:                               │
│                                                                         │
│  • Signed/unsigned mixing:                                              │
│      int i = -1; unsigned u = 1;                                        │
│      if (i < u) // FALSE! -1 becomes 4294967295                         │
│                                                                         │
│  • Implicit widening:                                                   │
│      char c = 0x80;                                                     │
│      if (c == 0x80) // May be FALSE! c sign-extends to 0xFFFFFF80       │
│                                                                         │
│  • Integer promotion in shifts:                                         │
│      unsigned char c = 1;                                               │
│      c << 31;  // Result is INT or UINT, NOT unsigned char              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                     CATEGORY 3: HISTORICAL ACCIDENTS                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Decisions made in the 1970s we still live with:                        │
│                                                                         │
│  • Fall-through in switch: Was it intentional? (Yes, for Duff's Device) │
│                                                                         │
│  • Array-to-pointer decay: Why arrays aren't first-class values         │
│                                                                         │
│  • Default int return: Functions without return type returned int       │
│                                                                         │
│  • Octal constants: Leading zero means octal (031 = 25 decimal)         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                     CATEGORY 4: UNDEFINED BEHAVIOR                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Code that seems to work until it doesn't:                              │
│                                                                         │
│  • Signed overflow:                                                     │
│      int x = INT_MAX; x++;  // UNDEFINED! Compiler can assume never     │
│                                                                         │
│  • Null pointer dereference (reading/writing):                          │
│      int *p = NULL; *p = 42;  // UNDEFINED, may not even crash!         │
│                                                                         │
│  • Sequence point violations:                                           │
│      i = i++;  // UNDEFINED! Don't do this                              │
│                                                                         │
│  • Uninitialized variables:                                             │
│      int x; return x;  // UNDEFINED! x has indeterminate value          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2.3 The “Bugs” We’ll Catalog

From Chapter 2 and throughout Expert C Programming:

THE MASTER BUG LIST
═══════════════════════════════════════════════════════════════════════════

BUG #1:  "NUL and NULL are different"
         NUL = '\0' (character zero)
         NULL = (void*)0 (null pointer)
         Confusion leads to type errors

BUG #2:  "The operator == has lower precedence than bitwise operators"
         if (flags & MASK == expected) // Always wrong!

BUG #3:  "Fall-through in switch is the default"
         case 1: x++;  // Falls through to case 2!
         case 2: y++;

BUG #4:  "String literals are not safely writable"
         char *s = "hello"; s[0] = 'H';  // Undefined behavior!

BUG #5:  "sizeof is not a function, parentheses are for types"
         sizeof(int) vs sizeof x  // Both valid

BUG #6:  "Arrays and pointers are different but look the same"
         extern char *s; vs char s[] = "hello"; // Linkage disaster

BUG #7:  "C has no true booleans (pre-C99)"
         if (ptr)  // Relies on any non-zero being truthy

BUG #8:  "Short-circuit evaluation depends on operator"
         if (a && b)  // b may not evaluate
         if (a & b)   // Both always evaluate

BUG #9:  "The comma operator exists and has lowest precedence"
         return a, b;  // Returns b, not a!

BUG #10: "Macro pitfalls are legion"
         #define SQUARE(x) x*x  // SQUARE(1+2) = 1+2*1+2 = 5, not 9

BUG #11: "Integer overflow is undefined for signed, defined for unsigned"
         INT_MAX + 1 is UB; UINT_MAX + 1 is 0

BUG #12: "The const keyword doesn't make things constant"
         const int n = 5; int arr[n];  // VLA in C99, error in C89

BUG #13: "Argument evaluation order is unspecified"
         f(i++, i++);  // Which increment happens first? Undefined!

BUG #14: "The ternary operator has surprising associativity"
         a ? b : c ? d : e  // Parses as a ? b : (c ? d : e)

BUG #15: "struct padding is implementation-defined"
         struct { char a; int b; } // sizeof might be 5, 8, or something else

3. Project Specification

3.1 What You Will Build

A Bug Museum: A collection of C source files that:

  1. Demonstrates each “language feature” from Chapter 2
  2. Shows correct and incorrect usage patterns
  3. Provides explanations of why each behavior occurs
  4. Includes compiler flag recommendations to catch each issue
  5. Offers defensive coding patterns to avoid problems

3.2 Functional Requirements

  1. Bug Demonstration Suite:
    • One test file per bug category
    • Code that compiles but exhibits surprising behavior
    • Expected vs actual output documented
    • Explanation of the C standard section involved
  2. Compiler Warning Integration:
    • Document which -W flags catch each bug
    • Show output with different warning levels
    • Compare GCC and Clang diagnostic differences
  3. Interactive Explorer (optional):
    • CLI tool to browse bugs by category
    • Run examples and see output
    • Show the “fix” for each bug
  4. Test Harness:
    • Automated tests that verify the bugs still exist
    • Tests that verify the fixes work
    • Platform-specific behavior documentation

3.3 Example Output

$ ./bug_museum --list
╔════════════════════════════════════════════════════════════════════════╗
║                    THE C LANGUAGE BUG MUSEUM                            ║
╠════════════════════════════════════════════════════════════════════════╣
║                                                                        ║
║  CATEGORY: Precedence Pitfalls                                         ║
║    [1] Bitwise AND vs Equality                                         ║
║    [2] Assignment in Condition                                         ║
║    [3] Shift Operator Precedence                                       ║
║                                                                        ║
║  CATEGORY: Type Conversion Traps                                       ║
║    [4] Signed/Unsigned Comparison                                      ║
║    [5] Char Sign Extension                                             ║
║    [6] Integer Promotion                                               ║
║                                                                        ║
║  CATEGORY: Historical Accidents                                        ║
║    [7] Switch Fall-Through                                             ║
║    [8] Octal Constant Surprise                                         ║
║    [9] Trigraph Replacement                                            ║
║                                                                        ║
║  CATEGORY: Undefined Behavior                                          ║
║    [10] Signed Integer Overflow                                        ║
║    [11] Null Pointer Dereference                                       ║
║    [12] Sequence Point Violation                                       ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

$ ./bug_museum --show 4
═══════════════════════════════════════════════════════════════════════════
BUG #4: Signed/Unsigned Comparison Trap
═══════════════════════════════════════════════════════════════════════════

THE BUG:
────────
int i = -1;
unsigned u = 1;
if (i < u) {
    printf("i is less\n");
} else {
    printf("u is less or equal\n");  // THIS PRINTS!
}

EXPECTED:  -1 is less than 1
ACTUAL:    "u is less or equal" prints

WHY IT HAPPENS:
───────────────
When comparing signed and unsigned integers, C converts the signed
value to unsigned. The conversion of -1 to unsigned is:
    (unsigned int)(-1) = UINT_MAX = 4294967295 (on 32-bit)

So the comparison becomes:
    if (4294967295 < 1)  // FALSE!

C STANDARD REFERENCE:
────────────────────
C11 §6.3.1.8 "Usual arithmetic conversions"
"...if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand
with signed integer type is converted to the type of the operand with
unsigned integer type."

COMPILER WARNING:
────────────────
gcc -Wsign-compare:
  warning: comparison of integer expressions of different signedness:
           'int' and 'unsigned int' [-Wsign-compare]

THE FIX:
────────
// Option 1: Cast the unsigned to signed (if values are small enough)
if (i < (int)u) { ... }

// Option 2: Use explicit range checking
if (i < 0 || (unsigned)i < u) { ... }

// Option 3: Use same types from the start
int i = -1;
int u = 1;  // Just use int if you need negative comparisons
═══════════════════════════════════════════════════════════════════════════

4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────────────────┐
│                        BUG MUSEUM ARCHITECTURE                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  bugs/                                                                  │
│  ├── 01_precedence/                                                     │
│  │   ├── bug01_bitwise_equality.c                                       │
│  │   ├── bug02_assignment_condition.c                                   │
│  │   └── bug03_shift_precedence.c                                       │
│  ├── 02_type_conversion/                                                │
│  │   ├── bug04_signed_unsigned.c                                        │
│  │   ├── bug05_char_extension.c                                         │
│  │   └── bug06_integer_promotion.c                                      │
│  ├── 03_historical/                                                     │
│  │   ├── bug07_switch_fallthrough.c                                     │
│  │   ├── bug08_octal_constants.c                                        │
│  │   └── bug09_trigraphs.c                                              │
│  └── 04_undefined_behavior/                                             │
│      ├── bug10_signed_overflow.c                                        │
│      ├── bug11_null_pointer.c                                           │
│      └── bug12_sequence_points.c                                        │
│                                                                         │
│  src/                                                                   │
│  ├── bug_museum.c           # Main CLI interface                        │
│  ├── bug_database.c         # Bug metadata and explanations             │
│  ├── compiler_checker.c     # Warning flag detection                    │
│  └── interactive.c          # Interactive browser                       │
│                                                                         │
│  tests/                                                                 │
│  ├── test_all_bugs.sh       # Verify bugs still exhibit behavior        │
│  └── test_all_fixes.sh      # Verify fixes work                         │
│                                                                         │
│  Makefile                   # Build with various warning levels         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

4.2 Bug Entry Data Structure

typedef struct {
    int id;
    const char *name;
    const char *category;
    const char *description;
    const char *code_buggy;
    const char *code_fixed;
    const char *expected_output;
    const char *actual_output;
    const char *explanation;
    const char *standard_ref;
    const char *warning_flags;
    const char *compiler_output;
} BugEntry;

BugEntry bug_database[] = {
    {
        .id = 4,
        .name = "Signed/Unsigned Comparison Trap",
        .category = "Type Conversion",
        .description = "Comparing signed and unsigned integers yields unexpected results",
        .code_buggy = "int i = -1;\nunsigned u = 1;\nif (i < u) ...",
        .code_fixed = "if (i < 0 || (unsigned)i < u) ...",
        .expected_output = "i is less",
        .actual_output = "u is less or equal",
        .explanation = "When comparing signed and unsigned...",
        .standard_ref = "C11 §6.3.1.8",
        .warning_flags = "-Wsign-compare",
        .compiler_output = "warning: comparison of integer expressions..."
    },
    // ... more entries
};

5. Implementation Guide

5.1 Phase 1: Bug Collection (Days 1-2)

Goal: Create source files demonstrating each bug

For each bug category, create a standalone C file that:

  1. Demonstrates the problematic behavior
  2. Shows the “expected” vs “actual” output
  3. Includes the fix
  4. Documents compiler warnings that would catch it

Example: bug04_signed_unsigned.c

/*
 * BUG #4: Signed/Unsigned Comparison Trap
 *
 * BEHAVIOR: When comparing a signed integer to an unsigned integer,
 * the signed value is converted to unsigned first, potentially changing
 * the comparison result completely.
 *
 * COMPILE: gcc -Wall -Wsign-compare bug04_signed_unsigned.c
 * WARNING: -Wsign-compare catches this
 */

#include <stdio.h>
#include <limits.h>

void demonstrate_bug(void) {
    printf("=== THE BUG ===\n");

    int i = -1;
    unsigned u = 1;

    printf("int i = %d\n", i);
    printf("unsigned u = %u\n", u);
    printf("\nQuestion: Is i < u?\n");
    printf("Human expectation: Yes, -1 is less than 1\n");

    if (i < u) {
        printf("C says: i IS less than u\n");
    } else {
        printf("C says: i is NOT less than u (SURPRISE!)\n");
    }

    printf("\nWHY: -1 as unsigned is %u\n", (unsigned)i);
    printf("So comparison becomes: %u < %u = %s\n",
           (unsigned)i, u, ((unsigned)i < u) ? "true" : "false");
}

void demonstrate_fix(void) {
    printf("\n=== THE FIX ===\n");

    int i = -1;
    unsigned u = 1;

    printf("Option 1: Explicit check for negative\n");
    if (i < 0 || (unsigned)i < u) {
        printf("  Result: i is less than u (CORRECT)\n");
    }

    printf("\nOption 2: Cast to signed if values fit\n");
    if (i < (int)u) {
        printf("  Result: i is less than u (CORRECT)\n");
    }
}

int main(void) {
    demonstrate_bug();
    demonstrate_fix();
    return 0;
}

5.2 Phase 2: Database and CLI (Days 3-4)

Goal: Build the interactive bug explorer

Create a CLI tool that:

  1. Lists all bugs by category
  2. Shows detailed information for each bug
  3. Compiles and runs bug examples
  4. Shows compiler warnings for each

5.3 Phase 3: Warning Integration (Days 5-6)

Goal: Document which compiler flags catch each bug

Create a matrix of:

  • Bug ID → GCC flags that warn
  • Bug ID → Clang flags that warn
  • Bug ID → MSVC flags that warn

5.4 Phase 4: Test Suite (Day 7)

Goal: Verify all bugs still exhibit expected behavior

Write tests that:

  1. Compile each bug example
  2. Run and capture output
  3. Verify output matches documentation
  4. Work across platforms

6. Hints in Layers

Hint 1: Starting Point

Start with the simplest bugs to demonstrate: precedence pitfalls. These are easy to show and understand:

// Bug: == has higher precedence than &
int flags = 5;
int FLAG = 1;

if (flags & FLAG == 1)  // Parsed as: flags & (FLAG == 1)
                        // = flags & 1 = flags & true = flags & 1
                        // Accidentally correct for FLAG=1!

// Use FLAG = 2 to show the bug:
FLAG = 2;
if (flags & FLAG == 1)  // = flags & (2 == 1) = flags & 0 = 0 = false
Hint 2: Compiler Flags Research

Key flags to research for each bug:

Bug Type GCC Flag Clang Flag
Precedence -Wparentheses -Wparentheses
Sign compare -Wsign-compare -Wsign-compare
Fallthrough -Wimplicit-fallthrough -Wimplicit-fallthrough
Overflow -ftrapv (runtime) -fsanitize=undefined

Compile with -Wall -Wextra -Wpedantic to get most warnings.

Hint 3: Testing Strategy

For each bug, create two tests:

# test_bug_exists.sh - Verify the bug behavior
compile_and_run bugs/bug04_signed_unsigned.c
expect_output "i is NOT less"

# test_fix_works.sh - Verify the fix works
compile_and_run fixes/fix04_signed_unsigned.c
expect_output "i is less"
Hint 4: Platform Differences

Some bugs behave differently on different platforms:

  • char signedness varies (signed on x86, unsigned on ARM by default)
  • sizeof(long) varies (4 on 32-bit, 8 on 64-bit, 4 on Windows 64-bit)
  • Struct padding varies by alignment requirements

Document these differences as part of the bug entry.


7. Testing Strategy

Test Categories

Category Purpose Method
Behavioral Verify bugs exhibit documented behavior Run and check output
Warning Verify compiler warnings trigger Compile and check stderr
Fix Verify fixes work correctly Run fixed versions
Platform Document platform differences Cross-compile and compare

Example Test Script

#!/bin/bash
# test_bug.sh - Test a single bug file

BUG_FILE=$1
EXPECTED=$2

# Compile with warnings
gcc -Wall -Wextra -Wpedantic -o /tmp/bugtest "$BUG_FILE" 2>/tmp/warnings.txt

# Run and capture output
OUTPUT=$(/tmp/bugtest 2>&1)

# Check for expected behavior
if echo "$OUTPUT" | grep -q "$EXPECTED"; then
    echo "PASS: $BUG_FILE"
else
    echo "FAIL: $BUG_FILE"
    echo "Expected: $EXPECTED"
    echo "Got: $OUTPUT"
fi

# Show any compiler warnings
if [ -s /tmp/warnings.txt ]; then
    echo "Warnings:"
    cat /tmp/warnings.txt
fi

8. Common Pitfalls & Debugging

Pitfalls When Building This Project

Pitfall Symptom Solution
Compiler optimizes away UB Bug doesn’t reproduce Use -O0 or volatile
Platform differences Tests fail on some systems Document and handle
Compiler version changes New warnings appear Document minimum versions
Interactive mode hangs Reading input incorrectly Use proper buffering

Debugging Tips

  1. Use Compiler Explorer (godbolt.org): See exactly what code is generated
  2. Compare GCC and Clang: Different compilers, different behaviors
  3. Use -E for preprocessor: See macro expansion bugs
  4. Use sanitizers: -fsanitize=undefined catches UB at runtime

9. Extensions & Challenges

Beginner Extensions

  • Add syntax highlighting to bug display
  • Create a “quiz mode” that asks what code will output
  • Generate HTML documentation of all bugs

Intermediate Extensions

  • Add Clang AST analysis to detect bugs automatically
  • Create a browser-based version
  • Add visualization of type conversion paths

Advanced Extensions

  • Build a static analyzer that detects these patterns
  • Create a training dataset for ML-based bug detection
  • Integrate with CI/CD to catch these bugs in real codebases

10. Real-World Connections

Industry Impact

These “language features” have caused real security vulnerabilities:

  • Heartbleed (2014): Integer overflow led to buffer over-read
  • Ping of Death (1996): Integer overflow in packet size calculation
  • Morris Worm (1988): Buffer overflow in fingerd
  • Countless others: CVE database is full of these patterns

Interview Relevance

Technical interviews often include these questions:

  1. “What will this code output?” (signed/unsigned comparison)
  2. “Is there a bug in this code?” (precedence issues)
  3. “What’s wrong with this switch statement?” (fallthrough)
  4. “Why might this crash?” (null pointer, UB)

11. Books That Will Help

Topic Book Chapter
C quirks catalog Expert C Programming Ch. 2 “It’s Not a Bug”
Undefined behavior Effective C (2nd ed) Ch. 11 “Undefined Behavior”
Type system C: A Reference Manual Ch. 4-6
Secure coding CERT C Coding Standard Throughout
Compiler behavior Advanced C and C++ Compiling Ch. 10-12

12. Self-Assessment Checklist

Understanding

  • I can explain why if (x & MASK == 0) is a bug
  • I understand why -1 < 1U is false
  • I know when switch fall-through is intentional vs accidental
  • I can identify sequence point violations
  • I understand the difference between undefined and implementation-defined behavior

Implementation

  • Each bug has a working demonstration file
  • Compiler warnings are documented for each bug
  • Fixes are provided and tested
  • The CLI browser works correctly
  • Tests pass on multiple platforms

Growth

  • I can spot these patterns in real code during review
  • I write code that avoids these pitfalls naturally
  • I can explain these bugs to junior developers
  • I’ve enabled appropriate warnings in my projects

13. Submission / Completion Criteria

Minimum Viable Completion

  • At least 10 bugs documented with demonstration code
  • Each bug has expected vs actual output documented
  • Basic CLI that lists and shows bugs
  • README explaining the project

Full Completion

  • All 15+ bugs from Chapter 2 documented
  • Interactive CLI with browse/run/fix capabilities
  • Compiler warning matrix (GCC, Clang, MSVC)
  • Automated test suite
  • Platform-specific behavior notes

Excellence (Going Above & Beyond)

  • Static analysis tool that detects these patterns
  • Web-based interactive version
  • Integration with CI/CD
  • Training materials for team education
  • Contributions to compiler warning improvements

14. Thinking Exercise

Before implementing, analyze these code snippets:

Exercise 1: What does this print?

#include <stdio.h>
int main(void) {
    int x = 1;
    printf("%d\n", x << 2 + 1);
    return 0;
}

Exercise 2: True or false: sizeof('a') == 1 in C

// Think carefully about the type of character constants in C

Exercise 3: What’s wrong with this macro?

#define MAX(a,b) a > b ? a : b
int x = MAX(1, 2) + 3;  // What is x?

Exercise 4: Does this loop terminate?

for (unsigned i = 10; i >= 0; i--) {
    printf("%u\n", i);
}

15. Interview Questions They’ll Ask

After completing this project, you’ll handle questions like:

  1. “What’s the difference between == and = precedence in C?”
    • Explain why if (c = getchar() != EOF) is wrong
  2. “Why is comparing signed and unsigned dangerous?”
    • Walk through the conversion rules
    • Show the -1 < 1U example
  3. “What is undefined behavior and why does it matter?”
    • Explain compiler optimization implications
    • Give examples (signed overflow, null deref)
  4. “What compiler flags do you always use?”
    • Discuss -Wall -Wextra -Werror
    • Explain which warnings catch which bugs
  5. “Debug this code snippet” (given buggy C code)
    • Apply knowledge of precedence, types, UB to find the bug

This guide was expanded from EXPERT_C_PROGRAMMING_DEEP_DIVE.md. For the complete learning path, see the project index.