Toter Code

Beschreibung

Toter Code bezieht sich auf Code, der in einem Programm existiert, aber niemals ausgeführt werden kann, oder Code, dessen Ergebnisse niemals verwendet werden. Dies umfasst unerreichbare Anweisungen (nach unbedingten Returns, Endlosschleifen oder unmöglichen Bedingungen), ungenutzte Variablen und Funktionen, Code-Pfade, die aufgrund konstanter Bedingungen niemals genommen werden können, und Berechnungen, deren Ergebnisse verworfen werden. Während toter Code selbst nicht ausgeführt wird, weist er auf logische Fehler hin, erhält Angriffsfläche und verdunkelt das tatsächliche Programmverhalten.

Risiko

Toter Code birgt mehrere Risiken. Er weist oft auf Logikfehler hin - der Programmierer erwartete, dass der Code ausgeführt wird, machte aber einen Fehler. Er erhöht den Wartungsaufwand und kann Entwickler bezüglich der beabsichtigten Funktionalität verwirren. Toter Code erhält unnötige Angriffsfläche - verwundbarer Code, der "nicht erreichbar sein kann", kann durch zukünftige Änderungen oder unerwartete Ausführungspfade erreichbar werden. Er kann Sicherheitsprobleme während Code-Reviews verbergen. Auf ressourcenbeschränkten Systemen verschwendet toter Code Speicher.

Lösung

Aktivieren Sie Compiler-Optimierungen und Warnungen, die toten Code erkennen (-Wunreachable-code, -Wunused). Verwenden Sie statische Analysewerkzeuge zur Identifizierung unerreichbaren Codes. Führen Sie regelmäßige Code-Bereinigung durch, um toten Code zu entfernen. Implementieren Sie Code-Coverage-Tests - Code, der nie abgedeckt wird, kann tot sein. Überprüfen Sie Logik, die unerreichbare Bedingungen zu erzeugen scheint. Behandeln Sie toten Code als potentiellen Bug-Indikator.

Häufige Auswirkungen

AuswirkungDetails
QualitätBereich: Code-Wartbarkeit

Toter Code verwirrt Entwickler und erhöht die Wartungskosten.
SicherheitBereich: Versteckte Schwachstellen

Toter Code kann Schwachstellen enthalten oder Logikfehler verdunkeln.
ZuverlässigkeitBereich: Logikfehler

Toter Code zeigt oft an, dass das Programm nicht wie beabsichtigt funktioniert.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Code nach unbedingtem Return
int process_data_vulnerable(int* data, int size) {
    if (data == NULL) {
        return ERROR_NULL;
    }

    if (size <= 0) {
        return ERROR_SIZE;
    }

    return process(data, size);

    // TOTER CODE: Wird nie erreicht!
    cleanup(data);  // Beabsichtigte Bereinigung passiert nie!
    log_completion();
}

// VERWUNDBAR: Bedingung immer falsch
void check_value_vulnerable(int value) {
    if (value > 100) {
        handle_high(value);
    }

    if (value > 100 && value < 50) {  // Unmögliche Bedingung!
        // TOTER CODE: Kann nie wahr sein
        handle_special(value);  // Bug: Dies sollte laufen!
    }
}

// VERWUNDBAR: Unerreichbar nach Endlosschleife
void server_loop_vulnerable() {
    while (1) {
        handle_request();
    }

    // TOTER CODE: Wird nie erreicht!
    cleanup_server();  // Ressourcen werden nie bereinigt!
    close_connections();
}

// VERWUNDBAR: Unerreichbarer Case
int handle_type_vulnerable(int type) {
    if (type < 0 || type > 10) {
        return ERROR_INVALID;
    }

    switch (type) {
        case 0:
            return handle_zero();
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
            return handle_normal();
        case 100:  // TOTER CODE: type kann nicht 100 sein!
            return handle_special();  // Wird nie ausgeführt!
    }

    return OK;  // Auch tot nach vollständigem Switch
}

// VERWUNDBAR: Ungenutzte Variable zeigt Bug an
int calculate_vulnerable(int a, int b) {
    int result = a + b;
    int intermediate = result * 2;  // Berechnet aber nie verwendet!

    return result;  // Sollte wahrscheinlich intermediate zurückgeben?
}

// VERWUNDBAR: Konstante Bedingung
void process_vulnerable(int* ptr) {
    int debug = 0;  // Auf 0 gesetzt = Debug deaktiviert

    if (debug) {  // Immer falsch!
        // TOTER CODE
        print_debug_info(ptr);
    }

    // Später könnte jemand denken Debug funktioniert...
    process_normal(ptr);
}

// VERWUNDBAR: Return in beiden Zweigen
int get_value_vulnerable(int flag) {
    if (flag) {
        return 1;
    } else {
        return 0;
    }

    // TOTER CODE: Wird nie erreicht
    log_result();  // Sollte das immer laufen?
    return -1;
}

// VERWUNDBAR: Ungenutzte Funktion (tote Funktion)
static void unused_helper_vulnerable() {
    // Diese gesamte Funktion ist toter Code
    // Kann Sicherheitsprobleme enthalten
    process_sensitive_data();
}
// VERWUNDBAR: C++ toter Code-Muster
class VerwundbarerProcessor {
public:
    int process(const Data& data) {
        validate(data);
        return compute(data);

        // TOTER CODE
        cleanup();  // Wird nie aufgerufen!
    }

    void handleError(int code) {
        throw std::runtime_error("Fehler");

        // TOTER CODE nach throw
        logError(code);  // Fehler nicht protokolliert!
        notifyAdmin();   // Admin nicht benachrichtigt!
    }

private:
    // Ungenutzte private Methode
    void deprecatedMethod() {
        // Toter Code - wird nie aufgerufen
        dangerousOperation();  // Versteckte Schwachstelle!
    }
};

// VERWUNDBAR: Template nie instanziiert
template<typename T>
void unusedTemplate(T value) {
    // Toter Code wenn nie instanziiert
    // Kann unentdeckte Bugs enthalten
    value.riskyOperation();
}

// VERWUNDBAR: Code nach std::exit
void shutdown_vulnerable() {
    save_state();
    std::exit(0);

    // TOTER CODE
    cleanup_resources();  // Wird nie ausgeführt!
}
// VERWUNDBAR: JavaScript toter Code
function processVulnerable(data) {
    if (!data) {
        return null;
    }

    return transform(data);

    // TOTER CODE
    validate(data);  // Validierung läuft nie!
}

// VERWUNDBAR: Konstante Bedingung
function checkVulnerable(value) {
    const DEBUG = false;

    if (DEBUG) {
        // Toter Code
        console.log('Debug:', value);
    }

    if (false) {  // Offensichtlich tot
        handleSpecialCase();
    }
}

// VERWUNDBAR: Unerreichbar nach throw
function validateVulnerable(input) {
    if (!input) {
        throw new Error('Eingabe erforderlich');
        console.log('Fehler geworfen');  // Toter Code
    }
}

// VERWUNDBAR: Ungenutzte Funktion
function unusedHelper() {
    // Toter Code - wird nie aufgerufen
    sensitiveOperation();
}

Lösungscode

// SICHER: Ordnungsgemäße Bereinigung vor Return
int process_data_safe(int* data, int size) {
    int result;

    if (data == NULL) {
        log_error("NULL data");
        return ERROR_NULL;
    }

    if (size <= 0) {
        log_error("Ungültige Größe");
        return ERROR_SIZE;
    }

    result = process(data, size);

    // Bereinigung VOR Return
    log_completion();

    return result;
}

// Oder Bereinigungsmuster verwenden
int process_data_safe_v2(int* data, int size) {
    int result = ERROR_UNKNOWN;

    if (data == NULL) {
        result = ERROR_NULL;
        goto cleanup;
    }

    if (size <= 0) {
        result = ERROR_SIZE;
        goto cleanup;
    }

    result = process(data, size);

cleanup:
    log_completion();
    return result;
}

// SICHER: Unmögliche Bedingung entfernen
void check_value_safe(int value) {
    if (value > 100) {
        handle_high(value);
    }

    // Entfernen oder unmögliche Bedingung beheben
    // Wenn spezielle Behandlung beabsichtigt war, Logik korrigieren:
    if (value > 50 && value <= 100) {
        handle_special(value);
    }
}

// SICHER: Ordnungsgemäße Schleife mit Exit-Bedingung
void server_loop_safe() {
    volatile int running = 1;

    while (running) {
        if (!handle_request()) {
            running = 0;  // Sauberen Exit ermöglichen
        }
    }

    // Jetzt erreichbar
    cleanup_server();
    close_connections();
}

// Oder break für kontrollierten Exit verwenden
void server_loop_safe_v2() {
    while (1) {
        if (should_shutdown()) {
            break;
        }
        handle_request();
    }

    cleanup_server();
}

// SICHER: Unerreichbaren Case entfernen
int handle_type_safe(int type) {
    if (type < 0 || type > 10) {
        return ERROR_INVALID;
    }

    switch (type) {
        case 0:
            return handle_zero();
        default:
            return handle_normal();
    }
}

// SICHER: Berechneten Wert verwenden
int calculate_safe(int a, int b) {
    int result = a + b;
    int intermediate = result * 2;

    return intermediate;  // Jetzt verwendet!
}

// Oder ungenutzte Berechnung entfernen
int calculate_safe_v2(int a, int b) {
    return a + b;  // Einfach, kein toter Code
}

// SICHER: Präprozessor oder const für Debug verwenden
#ifdef DEBUG
#define DEBUG_ENABLED 1
#else
#define DEBUG_ENABLED 0
#endif

void process_safe(int* ptr) {
#if DEBUG_ENABLED
    print_debug_info(ptr);  // Nur in Debug-Builds kompiliert
#endif
    process_normal(ptr);
}

// SICHER: Einzelner Return-Punkt
int get_value_safe(int flag) {
    int result = flag ? 1 : 0;

    log_result(result);  // Wird jetzt immer ausgeführt

    return result;
}

// SICHER: Ungenutzte Funktionen entfernen oder markieren
// Wenn Funktion für zukünftige Verwendung benötigt wird, dokumentieren:
#if 0  // Deaktiviert bis benötigt
static void future_feature() {
    // ...
}
#endif

// Oder vollständig entfernen wenn nicht benötigt
// SICHER: C++ mit ordnungsgemäßer Bereinigung
class SichererProcessor {
public:
    int process(const Data& data) {
        auto guard = createCleanupGuard();  // RAII Bereinigung
        validate(data);
        return compute(data);
        // guards Destruktor führt cleanup() aus
    }

    void handleError(int code) {
        // VOR throw protokollieren
        logError(code);
        notifyAdmin();
        throw std::runtime_error("Fehler");
    }

    // Ungenutzte Methoden entfernen oder als [[maybe_unused]] markieren
    [[maybe_unused]]
    void maybeUsedMethod() {
        // Absichtlich für zukünftige Verwendung behalten
    }
};

// SICHER: If-constexpr für Kompilierzeit-Entfernung von totem Code (C++17)
template<bool Debug>
void process_safe() {
    if constexpr (Debug) {
        // Nur kompiliert wenn Debug true ist
        printDebugInfo();
    }
    doWork();
}

// SICHER: Sicherstellen dass Templates instanziiert und getestet werden
template<typename T>
void usedTemplate(T value) {
    static_assert(std::is_copy_constructible_v<T>,
                 "T muss kopier-konstruierbar sein");
    value.operation();
}

// Explizite Instanziierung zum Testen
template void usedTemplate<ConcreteType>(ConcreteType);

// SICHER: Kein Code nach [[noreturn]]
[[noreturn]] void shutdown_safe() {
    save_state();
    cleanup_resources();  // VOR exit aufrufen
    std::exit(0);
}
// SICHER: Ordnungsgemäßer Code-Fluss
function processSafe(data) {
    if (!data) {
        logError('Keine Daten');
        return null;
    }

    // VOR transform validieren
    if (!validate(data)) {
        logError('Ungültige Daten');
        return null;
    }

    return transform(data);
}

// SICHER: Debug-Code entfernen oder ordnungsgemäß verwenden
const DEBUG = process.env.DEBUG === 'true';

function checkSafe(value) {
    if (DEBUG) {
        console.log('Debug:', value);
    }
    return validate(value);
}

// SICHER: Nichts nach throw
function validateSafe(input) {
    if (!input) {
        console.log('Validierung fehlgeschlagen');
        throw new Error('Eingabe erforderlich');
    }
    return true;
}

// SICHER: Ungenutzte Funktionen entfernen oder exportieren
// Wenn zum Testen benötigt, exportieren:
export function testHelper() {
    // Kann getestet und verwendet werden
}

// ESLint wird markieren:
// - "no-unreachable"
// - "no-unused-vars"
// - "no-constant-condition"

Ausgenutzt in der Praxis

Versteckte Hintertüren

Toter Code wurde verwendet, um Hintertüren zu verstecken - Code, der unerreichbar erscheint, aber durch spezifische Eingaben oder Code-Modifikationen aktiviert werden kann.

Ignorierte Sicherheitsprüfungen

Sicherheitsvalidierungscode, der tot war (nach vorzeitigen Returns), hat zu Schwachstellen geführt, wenn die Prüfungen nie ausgeführt wurden.

Wartungsfehler

Toter Code, der "funktionieren sollte", hat zu Sicherheitsproblemen geführt, wenn Entwickler annahmen, er würde funktionieren.


Tools zum Testen

  • GCC/Clang Warnungen — -Wunreachable-code, -Wunused.
  • Coverity — erkennt toter Code-Muster.
  • ESLint — no-unreachable, no-unused-vars Regeln.
  • Code-Coverage-Tools — identifizieren nie ausgeführten Code.

CVE-Beispiele

  • Verschiedene Schwachstellen, bei denen toter Sicherheitscode ein falsches Sicherheitsgefühl gab.
  • Hintertüren, die in scheinbar toten Code-Abschnitten versteckt waren.
  • Logikfehler durch Missverständnis, welcher Code ausgeführt wird.

Referenzen

  1. MITRE. "CWE-561: Dead Code." https://cwe.mitre.org/data/definitions/561.html
  2. CERT C. "MSC12-C: Detect and remove code that has no effect or is never executed."