Řešení varování statické analýzy pro podmínky Always-False

Jak vyřešit varování statické analýzy pro podmínky Always-False a nedosažitelné větve

Váš statický analyzátor označil podmínku jako vždy nepravdivou. Varování vypadá v závislosti na vašem nástroji podobně:

  • SonarQube: „Podmíněné výrazy, které jsou vždy pravdivé nebo nepravdivé, mohou vést k nedosažitelnému kódu.“ (RSPEC-2583)
  • IntelliJ / Inspectopedia: „Kód není analyzován, protože podmínka je staticky vyhodnocena jako nepravdivá.“
  • Checkmarx / Fortify: „Nedosažitelný kód, detekována mrtvá větev“
  • ESLint: „podmínka bez konstanty, neočekávaná konstantní podmínka“
  • Pylint: „W0125: Použití podmíněného příkazu s konstantní hodnotou“

Všechny tyto tvrzení říkají totéž: analyzátor v době analýzy, aniž by spustil kód, prokázal, že určitá větev podmíněného příkazu se nikdy nemůže spustit. Podmínka, která tuto větev chrání, je vždy nepravdivá. Všechno uvnitř této větve je mrtvý kód.

Tato příručka popisuje, co způsobuje tato varování, jak určit, do které kategorie každé varování spadá, správné rozlišení pro každou kategorii, kdy je vhodné potlačení a co podmínky always-false ve starším kódu COBOL odhalují o rozsahu migrace.

Vyřazená obchodní pravidla stále ve vašem kódu

SMART TS XL Identifikuje větve EVALUATE odkazující na kódy produktů, hodnoty stavu a typy zákazníků, které již v produkčních datech neexistují.

ZJISTĚTE VÍCE…

Jak statická analýza detekuje podmínky Always-False

Moderní statické analyzátory používají k detekci podmínek typu „always-false“ symbolické provádění a analýzu datového toku, nikoli jednoduché porovnávání vzorů. Analyzátor sleduje možné hodnoty každé proměnné v každém bodě programu. Když dosáhne podmíněné podmínky, vyhodnotí, zda může být podmínka někdy pravdivá za daných podmínek proměnných.

Tři primární mechanismy produkují detekci vždy falešných dat:

Symbolické provedení. Analyzátor přiřazuje symbolické hodnoty proměnným a sleduje, jak operace tyto hodnoty omezují. Pokud je proměnná x je přidělen 0 na řádku 3 a porovnáno x > 10 na řádku 7 bez mezilehlého přiřazení je podmínka prokazatelně nepravdivá.

Omezení typového systému. Některé podmínky nejsou možné vzhledem k typu proměnné. Porovnání odkazu na objekt, který není nulový, s hodnotou null po provedení nenullového tvrzení nebo porovnání hodnoty výčtu s případem mimo jeho definovanou množinu.

Zjednodušení booleovské algebry. Složené podmínky, které zjednodušují na false bez ohledu na hodnoty proměnných: (a && !a), (x > 10 && x < 5), (!flag || false).

Pravidlo RSPEC-2583 v SonarQube konkrétně používá symbolické provádění: sleduje stav proměnných prostřednictvím grafu řídicího toku a označuje podmínky, u kterých symbolický stav činí podmínku prokazatelně nepravdivou. Varování „podmínka staticky vyhodnocena jako nepravdivá“ v IntelliJ používá stejný přístup.

Čtyři základní příčiny

Správné vyřešení vždy falešného varování závisí na správné identifikaci, která ze čtyř hlavních příčin se uplatňuje. Řešení se u každé z nich liší.

Základní příčina 1: Logický rozpor

Podmínka obsahuje logickou nemožnost, dvě dílčí podmínky, které nemohou být obě pravdivé současně, nebo složenou podmínku, která se redukuje na false booleovskou algebrou.

Jáva

// Example: mutually exclusive subconditions
int score = getScore();
if (score > 90 && score < 50) {  // Always false: no integer is both > 90 and < 50
    applyBonus();                 // Never executes
}

// Example: boolean tautology negation
boolean active = isActive();
if (active && !active) {  // Always false: a && !a = false
    sendNotification();   // Never executes
}

// Example: range check already guaranteed
if (value >= 0) {
    if (value < 0) {  // Always false: already know value >= 0
        handleNegative();  // Never executes
    }
}

Rozlišení: Opravte logiku. Buď podmínka obsahuje překlep (vývojář měl na mysli score > 90 || score < 50) nebo by měla být větev odstraněna. V příkladu kontroly rozsahu vnitřní if je nadbytečné a mělo by být smazáno.

Hlavní příčina 2: Nahromaděné změny obchodních pravidel

Toto je nejvýznamnější příčina v dlouhodobých kódových bázích. Podmínka, která byla platná a smysluplná v době svého zápisu, se trvale stala nepravdivou v důsledku následných změn v kódu kolem ní, aniž by kdokoli odstranil nyní mrtvou větev.

Jáva

// 2018: processType could be A, B, or C
// 2023: type C was deprecated and removed from the system
// The processType field is now guaranteed to never equal "C"
// but nobody removed the branch that handled it

public void process(String processType) {
    if (processType.equals("A")) {
        handleTypeA();
    } else if (processType.equals("B")) {
        handleTypeB();
    } else if (processType.equals("C")) {  // Always false since 2023
        handleTypeC();                      // 47 lines of dead business logic
    }
}

krajta

# Python equivalent: accumulated rule changes
def calculate_rate(customer_tier: str) -> float:
    if customer_tier == "GOLD":
        return 0.05
    elif customer_tier == "SILVER":
        return 0.03
    elif customer_tier == "BRONZE":
        # BRONZE tier was eliminated in Q2 2021
        # All BRONZE customers were migrated to SILVER
        # This branch has been dead for 4+ years
        return 0.01  # Never executes
    return 0.0

Řešení: Ověřte obchodní kontext a poté zcela odstraňte nefunkční větev. Obchodní pravidlo, které umožnilo vznik tohoto stavu, se změnilo. Kód, který jej implementoval, již není potřeba. Odstranění zdokumentujte ve zprávě potvrzení: „Odebrána větev s tarifem BRONZE, úroveň zrušena ve 2. čtvrtletí 2021, všichni zákazníci migrovali na STŘÍBRNOU.“

Hlavní příčina 3: Obranné programování pro zajištění budoucnosti

Některé podmínky, které jsou vždy nepravdivé, jsou záměrné, vývojář je napsal s vědomím, že podmínka nemůže být v současné době pravdivá, jako obrannou ochranu proti budoucím změnám kódu, které by ji mohly splnit.

Jáva

// SonarQube RSPEC-2583 explicitly exempts these:
final boolean DEBUG_MODE = false;
if (DEBUG_MODE) {                // Always false -- intentional
    dumpDiagnostics();           // Not unreachable in intent, only in fact
}

// Type narrowing that appears redundant
String value = getValue();
if (value == null || value.isEmpty()) {
    return;
}
// At this point, static analysis knows value is non-null and non-empty
if (value == null) {  // Always false -- already checked above
    throw new IllegalStateException(); // Defensive guard
}

SonarQube RSPEC-2583 explicitně vyčlení výjimku: „Když je podmínka jediným finálním booleovským číslem“ nebo „když je podmínka doslova pravdivá nebo nepravdivá“, tyto výjimky jsou považovány za úmyslné a nejsou označeny příznakem.

Řešení: Pokud je podmínkou ladicí příznak nebo konstanta kompilace: ponechte ji a přidejte anotaci potlačení, pokud ji nástroj označí. Pokud se jedná o obrannou kontrolu null-control nebo ochranu rozsahu, která se jeví jako redundantní, ale skutečně chrání: restrukturalizujte kód tak, aby byla ochrana explicitní na jiné úrovni, nebo ji potlačte s dokumentovaným zdůvodněním.

Hlavní příčina 4: Falešně pozitivní výsledky nástroje z konfigurace za běhu

Statické analyzátory nevidí běhovou konfiguraci, proměnné prostředí, reflexi, zpracování anotací ani vkládání závislostí. Kód, který se analyzátoru jeví jako always-false, může být dosažitelný prostřednictvím běhového mechanismu, který analyzátor nedokáže modelovat.

Jáva

// IntelliJ / Inspectopedia note: "This may cause false-positives if any kind
// of code postprocessing is used, for example, if an annotation processor
// later replaces the method body with something useful."

@ConditionalOnProperty(name = "feature.experimental", havingValue = "true")
public class ExperimentalFeature {
    // This class may appear unreachable if the property is false in the
    // test configuration the analyzer uses -- but it IS reachable in prod
}

// Method contract that the analyzer cannot see
void run() {
    performAction();
    System.out.println("Done"); // IntelliJ flags as unreachable
}
@Contract("-> _") // Tells the analyzer: method does NOT always throw
static void performAction() { throw new AssertionError(); }

Rozlišení: Přidejte příslušnou anotaci smlouvy metody (@Contract pro IntelliJ, @NotNull/@Nullable pro Kotlin, aserce typů pro TypeScript), aby analyzátor získal potřebné informace za běhu. Pokud není k dispozici rozlišení založené na anotacích, použijte mechanismus potlačení nástroje se zdokumentovaným zdůvodněním.

COBOL: Kde vždy nepravdivé podmínky odhalují desetiletí mrtvé logiky

V COBOLu a starších mainframeových systémech mají podmínky typu always-false jiný charakter než v moderním aplikačním kódu a jiný význam pro týmy, které je udržují nebo modernizují.

Programy v COBOLu nejsou refaktorovány tak, jako je tomu u moderního kódu. Když se změní obchodní pravidlo, reviduje se daňový kód, odstraní se produktová úroveň nebo se aktualizuje regulační požadavek, běžnou praxí je přidat novou logiku bez odstranění staré. Stará větev EVALUATE WHEN nebo IF zůstává ve zdrojovém kódu, chráněná podmínkou, která nikdy nemůže být pravdivá vzhledem k aktuálnímu stavu zpracovávaných dat.

cobol

       EVALUATE WS-PRODUCT-TYPE
           WHEN 'STD'
               PERFORM CALC-STANDARD-RATE
           WHEN 'PRM'
               PERFORM CALC-PREMIUM-RATE
           WHEN 'BAS'
      *        BAS product type retired June 2009
      *        All BAS customers converted to STD
      *        This branch has been dead for 15+ years
               PERFORM CALC-BASIC-RATE
           WHEN 'LEG'
      *        LEG (legacy) type eliminated in 2001 system migration
      *        Should have been removed but was left as "just in case"
               PERFORM CALC-LEGACY-RATE
           WHEN OTHER
               PERFORM HANDLE-UNKNOWN-TYPE
       END-EVALUATE.

Dvě mrtvé větve v tomto VYHODNOTĚNÍ, BAS a LEG, nejsou chyby. Jsou to historické artefakty: obchodní pravidla, která byla platná v určitých bodech historie systému a stala se trvale nedostupnými, když byly tyto typy produktů vyřazeny. Nikdo je neodstranil, protože si nikdo nebyl jistý, zda by mohly být stále potřeba, a cena za chybu se zdála vyšší než cena za jejich ponechání v platnosti.

Co odhalují vždy nepravdivé podmínky v COBOLu:

Mrtvé větve představují obchodní logiku, která již neplatí. PERFORM CALC-BASIC-RATE a PERFORM CALC-LEGACY-RATE Cíle mohou být samy o sobě celé odstavce nebo části kódu v COBOLu, potenciálně stovky řádků, které jsou podobně nefunkční. Podmínka always-false je vstupním bodem do stromu nefunkčního kódu, který se šíří prostřednictvím relací PERFORM.

V programu v COBOLu o délce 100 000 řádků mohou mrtvé větve z vyřazených obchodních pravidel tvořit 20–35 % celkového kódu. Toto procento představuje rozsah migrace, který není nutné v modernizačním programu převádět, testovat ani validovat. Jeho identifikace před plánováním migrace vede k přímému snížení rozsahu a snížení rozsahu ve fázi plánování je nejhodnotnější aplikací analýzy stavu always-false.

Strom rozhodnutí o řešení

Always-false condition warning received
            │
            ▼
    Is this a compile-time constant
    or debug flag? (DEBUG = false, etc.)
            │
     Yes ───┼─── No
            │         │
    Intentional.      ▼
    Suppress or   Does the condition
    leave as-is.  contain a logic error?
                  (a > 10 && a < 5, etc.)
                        │
                 Yes ───┼─── No
                        │          │
                  Fix the        Has the condition
                  logic bug.     become false due to
                                 a past business rule
                                 change?
                                        │
                                 Yes ───┼─── No
                                        │          │
                              Verify,         Could a runtime
                              then remove     mechanism make
                              the dead        it reachable?
                              branch.               │
                                           Yes ──── No
                                                    │
                                             Add contract
                                             annotation.     Remove the
                                                             dead branch.

Potlačení: Kdy a jak

Potlačení je vhodné, pokud je podmínka skutečně úmyslná a nástroj nemůže získat informace o smlouvě, aby pochopil proč. Potlačení není vhodné jako zkratka k vyhnutí se zkoumání skutečného významu varování.

Java (SonarQube):

Jáva

@SuppressWarnings("java:S2583")
if (DEBUG_MODE) {  // Intentional: compile-time debug flag
    dumpState();
}

Java (IntelliJ):

Jáva

//noinspection ConstantConditions
if (possiblyNull != null) {  // Analyzer cannot see the runtime injection
    process(possiblyNull);
}

Python (Pylint):

krajta

if False:  # pylint: disable=W0125
    # Intentional: placeholder for future implementation
    pass

C# (SonarQube):

csharp

#pragma warning disable S2589
if (IsDebugBuild) {  // Compile-time constant -- intentional
    LogDiagnostics();
}
#pragma warning restore S2589

COBOL (SMART TS XL / vlastní potlačení komentáře):

cobol

      *>> SUPPRESS UNREACHABLE: BAS branch retained for audit trail
           WHEN 'BAS'
               PERFORM CALC-BASIC-RATE

Disciplína potlačující potlačení: Každé potlačení by mělo být doprovázeno komentářem, který vysvětluje, proč je podmínka úmyslná, nejen to, že je potlačována. Komentář k potlačení, který říká // suppress unreachable Je horší než žádné potlačení, skryje varování bez vysvětlení, proč je varování chybné. Potlačení, které říká // suppress: DEBUG_MODE is a compile-time constant, branch is retained for IDE visibility je přijatelný.

Jak SMART TS XL Identifikuje vždy nepravdivé podmínky v rozsahu portfolia

SMART TS XLJe statická analýza kódu Aplikuje analýzu toku dat a řídicího toku napříč COBOLem, JCL, Javou, Pythonem a všemi ostatními jazyky v prostředí k identifikaci podmínek, které jsou staticky prokazatelné jako vždy nepravdivé, v rozsahu portfolia, napříč tisíci programy současně, nikoli v jednom souboru najednou ve vývojářském IDE.

Konkrétně pro portfolia COBOL to znamená: každá klauzule EVALUATE WHEN a každá podmínka IF v každém programu je analyzována s ohledem na známá omezení dat, která do ní proudí. Podmínky, které odkazují na kódy produktů, stavové příznaky nebo kódy typů, jejichž platné sady hodnot se v průběhu historie programu změnily, jsou označeny jako potenciálně mrtvé větve. Výstupem je inventář mrtvého kódu: každý program, každá mrtvá větev a odhadovaný počet řádků mrtvého kódu dosažitelných z každé podmínky typu always-false.

Analýza modernizace starších systémů převádí tento inventář do redukce rozsahu: každá nefunkční větev je vyloučena ze specifikace migrace před zahájením jakéhokoli konverzního úsilí. Program, který má zdánlivě 2 000 řádků obchodní logiky, může mít 600 nefunkčních řádků obchodní logiky z vyřazených typů produktů, eliminovaných úrovní zákazníků a zastaralých výpočetních cest. Migrace 1 400 řádků místo 2 000 představuje 30% redukci rozsahu daného programu a nefunkční větve jsou nejrizikovější částí rozsahu k migraci, protože nikdo neví, co měly původně dělat.

Funkce podnikového vyhledávání umožňuje dotazování na inventář s hodnotou always-false: vyhledá každý program v COBOLu, který obsahuje klauzuli EVALUATE WHEN odkazující na kód produktu, který není v aktuálním katalogu produktů, každý program, který kontroluje hodnotu příznaku stavu, která v databázi neexistuje od určitého roku, každou podmínku IF, která odkazuje na pole datové struktury, jehož účel byl od zapsání podmínky přepracován. Tyto dotazy vytvářejí cílený inventář mrtvého kódu, jehož vytvoření by v rámci velkého portfolia COBOLu trvalo měsíce.

Varování je obvykle správné

Varování statické analýzy pro podmínky always-false jsou ve většině případů přesnými zjištěními. Podmínka je vždy nepravdivá. Větev se nikdy neprovede. Kód uvnitř ní je mrtvý.

Vhodnou reakcí není potlačení, ale vyšetřování. Která z hlavních příčin se uplatňuje? Logický rozpor, nahromaděná změna obchodních pravidel, úmyslné obranné programování nebo falešně pozitivní výsledek nástroje z běhové konfigurace? Každá z nich má správné řešení. Tři ze čtyř vedou k odstranění nefunkčního kódu. Jedna vede k potlačení s odůvodněním.

U starších systémů COBOL není analýza stavu „always-false“ v portfoliovém měřítku primárně cvičením pro kvalitu kódu, ale spíše cvičením pro redukci rozsahu. Mrtvé větve, které se hromadí v průběhu desetiletí změn obchodních pravidel, představují migrační úsilí, které by nemělo být vynakládáno. Jejich nalezení před uzamčením migračního plánu je nejhodnotnější aplikací analýzy stavu „always-false“ pro jakýkoli tým plánující program modernizace COBOLu.

Analyzátor vám říká pravdu. Poslouchejte ho.