Aikido

Come individuare e correggere le contraddizioni logiche nel codice

Bug logico

Regola
Rileva contraddittorie o impossibili logica
Codice che verifica le condizioni dopo che 
già stato violate, oppure presuppone afferma
che sono impossibili dato le flusso di controllo.

Linguaggi supportati: 45+

Introduzione

Si verifica una logica contraddittoria quando il codice verifica condizioni di cui si sa già, in base al flusso di controllo precedente, che sono vere o false. Ciò accade dopo una rifattorizzazione, quando l’ordine delle verifiche viene modificato, oppure quando gli sviluppatori aggiungono controlli difensivi senza comprendere quali garanzie esistano già. Una funzione che verifica if (user !== null) dopo aver chiamato user.email presenta una logica contraddittoria; il controllo di null viene eseguito troppo tardi. Queste incongruenze logiche indicano problemi più profondi nell'organizzazione del codice o una comprensione insufficiente di ciò che garantisce ciascun percorso di esecuzione.

Perché è importante

Implicazioni per la sicurezza: una convalida errata crea una pericolosa illusione di sicurezza. Quando i controlli di sicurezza vengono effettuati solo dopo che i dati sono già stati utilizzati, gli aggressori possono sfruttare il lasso di tempo che intercorre prima che avvenga la convalida. Il codice che verifica le autorizzazioni dell’utente dopo l’esecuzione di operazioni con privilegi non offre alcuna protezione effettiva, ma solo commenti fuorvianti in materia di sicurezza.

Manutenibilità del codice: una logica contraddittoria suggerisce che il codice non corrisponda al modello mentale dello sviluppatore. Qualcuno ha ritenuto necessario verificare una condizione ma l’ha inserita nel punto sbagliato, oppure il codice è stato rifattorizzato senza aggiornare i controlli correlati. I futuri manutentori non possono essere certi che la validazione sia presente dove necessario, il che li costringe a esaminare intere funzioni per comprendere quali siano le garanzie effettive.

Indicatori di bug: le condizioni impossibili raramente si presentano isolatamente. Esse segnalano problemi più profondi, quali la mancanza di una gestione degli errori, ipotesi errate sui contratti delle funzioni o un refactoring non riuscito. Un controllo che non può mai essere eseguito spesso indica che altrove manca un altro controllo che avrebbe dovuto impedire il verificarsi di tale stato.

Esempi di codice

❌ Non conforme:

function processOrder(order) {
    if (!order) {
        return { error: 'Order required' };
    }

    const total = order.items.reduce(
        (sum, item) => sum + item.price,
        0
    );

    if (order.items && order.items.length > 0) {
        applyDiscount(order);
    }

    if (total < 0) {
        throw new Error('Invalid total');
    }

    return { total, status: 'processed' };
}

Perché è sbagliato: Il codice chiama order.items.reduce() che va in crash se `items` è null oppure non definito, quindi verifica se gli elementi esistono in seguito. Il total < 0 Questa verifica è inoltre contraddittoria, poiché la funzione `reduce` restituisce sempre valori non negativi quando si sommano i prezzi.

✅ Conforme:

function processOrder(order) {
    if (!order || !order.items || order.items.length === 0) {
        return { error: 'Valid order with items required' };
    }

    const hasInvalidPrice = order.items.some(
        item => typeof item.price !== 'number' || item.price < 0
    );

    if (hasInvalidPrice) {
        throw new Error('Invalid item prices');
    }

    const total = order.items.reduce(
        (sum, item) => sum + item.price,
        0
    );

    if (order.items.length >= 5) {
        applyBulkDiscount(order);
    }

    return { total, status: 'processed' };
}

Perché è importante: tutte le operazioni di convalida avvengono prima dell'utilizzo dei dati, i controlli vengono eseguiti in ordine logico e le condizioni rispecchiano i requisiti effettivi. La funzione convalida gli input in anticipo, quindi elabora i dati validi senza controlli ridondanti o contraddittori.

Conclusione

Effettua la convalida prima di utilizzare i dati, non dopo. Esamina le condizioni che sembrano "difensive", ovvero quelle che compaiono dopo che i dati sono già stati consultati o modificati. Durante la rifattorizzazione, aggiorna o rimuovi le convalide correlate per mantenere la coerenza logica in tutta la funzione.

Domande frequenti

Hai delle domande?

Come faccio a distinguere la programmazione difensiva dalla logica contraddittoria?

La programmazione difensiva verifica la validità degli input ai confini delle funzioni prima del loro utilizzo. La logica contraddittoria effettua la verifica dopo l'utilizzo o controlla condizioni già garantite dal codice precedente. La tempistica e la necessità sono fondamentali. Se l'oggetto `user` è già stato dereferenziato con `user.email`, verificare successivamente se (user) è contraddittorio, non difensivo.

E per quanto riguarda il restringimento dei tipi in TypeScript?

L'analisi del flusso di controllo di TypeScript tiene traccia di ciò che è possibile in ogni punto. Se TypeScript consente un controllo, la condizione potrebbe essere raggiungibile. Tuttavia, il JavaScript in fase di esecuzione non impone questi tipi, quindi possono comunque esistere controlli in fase di esecuzione contraddittori nonostante la sicurezza dei tipi. Concentrati sul flusso di controllo in fase di esecuzione, non solo sui tipi statici.

Una logica contraddittoria può causare vulnerabilità di sicurezza?

Sì, quando i controlli di sicurezza vengono effettuati dopo l'esecuzione di operazioni con privilegi. Il controllo delle autorizzazioni dopo le operazioni di scrittura sul database, la convalida degli input dopo l'esecuzione di una query SQL o la verifica dell'autenticazione dopo l'esposizione di dati sensibili creano tutte vulnerabilità del tipo "time-of-check-time-of-use". Affinché la sicurezza sia efficace, la convalida deve precedere l'azione.

E che dire del codice per la gestione degli errori che sembra impossibile?

I gestori di errori per eccezioni che non possono verificarsi, vista la convalida effettuata in precedenza, potrebbero indicare una programmazione eccessivamente prudente o una gestione degli errori obsoleta, risalente a prima dell'aggiunta della convalida. Verifica se ogni percorso di errore sia effettivamente raggiungibile. Se non lo è, rimuovilo per semplificare il codice ed evitare di fuorviare i futuri manutentori.

Come faccio a individuare le contraddizioni logiche nel codice esistente?

Cerca i controlli di validazione successivi all'accesso ai dati, le condizioni che risultano sempre vere o false in base ai rami precedenti e la gestione degli errori per stati già prevenuti. Gli strumenti di copertura del codice aiutano a identificare i rami irraggiungibili. La revisione manuale dei modelli di validazione rivela controlli che vengono eseguiti troppo tardi o che verificano condizioni impossibili.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.