Rediger

Del via


Warning C26430

Symbol is not tested for nullness on all paths.

C++ Core Guidelines: F.23: Use a not_null<T> to indicate that "null" isn't a valid value

If code ever checks pointer variables for null, it should do so consistently and validate pointers on all paths. Sometimes overaggressive checking for null is still better than the possibility of a hard crash in one of the complicated branches. Ideally, such code should be refactored to be less complex (by splitting it into multiple functions), and to rely on markers like gsl::not_null. These markers allow the code to isolate parts of the algorithm that can make safe assumptions about valid pointer values. The rule TEST_ON_ALL_PATHS helps to find places where null checks are inconsistent (meaning assumptions may require review). Or, it finds actual bugs where a potential null value can bypass null checks in some of the code paths.

Remarks

This rule expects that code dereferences a pointer variable so that a null check (or enforcement of a non-null value) would be justified. If there's no dereference, the rule is suspended.

The current implementation handles only plain pointers (or their aliases) and doesn't detect smart pointers, even though null checks are applicable to smart pointers as well.

A variable is marked as checked for null when it's used in the following contexts:

  • as a symbol expression in a branch condition, for example, in if (p) { ... };
  • in non-bitwise logical operations;
  • in comparison operations where one operand is a constant expression that evaluates to zero.

Implicit null checks are assumed when a pointer value is assigned from:

  • an allocation performed with throwing operator new;
  • a pointer obtained from a type marked with gsl::not_null.

Example

inconsistent testing reveals logic error

void merge_states(const state *left, const state *right) // C26430
{
    if (*left && *right)
        converge(left, right);
    else
    {
        // ...
        if (!left && !right)                            // Logic error!
            discard(left, right);
    }
}

inconsistent testing reveals logic error - corrected

void merge_states(gsl::not_null<const state *> left, gsl::not_null<const state *> right)
{
    if (*left && *right)
        converge(left, right);
    else
    {
        // ...
        if (*left && *right)
            discard(left, right);
    }
}

Heuristics

When ensuring that a dereference of a pointer isn't null, this rule doesn't require every dereference to have a prior null check. Instead, it requires a null check before first dereference of the pointer. The following function doesn't trigger C26430:

void f(int* p)
{
    if (p)
        *p = 1;
    *p = 2;
}

The following function generates C26430 because there's a path to assign *p without a null check:

void f(bool b, int* p)
{
    if (b && p)
        *p = 1;
    *p = 2;
}

Rules C26822 and C26823 apply to dereferencing a (possibly) null pointer.

This rule doesn't do full data flow tracking. It can produce incorrect results in cases where indirect checks are used, such as when an intermediate variable holds a null value and is later used in a comparison.

See also

C26822
C26823