https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126819

            Bug ID: 126819
           Summary: [analyzer] false fd-leak warning on a throwing edge in
                    a noexcept destructor
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Keywords: diagnostic
          Severity: normal
          Priority: P3
         Component: analyzer
          Assignee: dmalcolm at gcc dot gnu.org
          Reporter: hello at bjornpagen dot com
  Target Milestone: ---
              Host: aarch64-apple-darwin24
            Target: aarch64-apple-darwin24
             Build: aarch64-apple-darwin24

On Darwin, the analyzer reports an fd leak on the exception edge from close
inside an implicitly noexcept destructor. The SDK declaration for close has no
nothrow attribute, so the analyzer assumes that close can throw. A throw from
this destructor would call std::terminate. It cannot return to running code
with a leaked descriptor.

Testcase:
#include <sys/socket.h>
#include <unistd.h>

class Fd {
public:
        Fd() = default;
        explicit Fd(int value) noexcept : value_{value} {}
        Fd(Fd const&) = delete;
        auto operator=(Fd const&) -> Fd& = delete;
        ~Fd() {
                if (value_ >= 0) {
                        ::close(value_);
                }
        }
        [[nodiscard]] auto get() const noexcept -> int { return value_; }
private:
        int value_ = -1;
};

struct Server {
        Fd listener{};
};

auto accept_one(Server& server) noexcept -> int {
        auto descriptor = Fd{::accept(server.listener.get(), nullptr,
nullptr)};
        return descriptor.get() >= 0 ? 0 : -1;
}

Command:
g++-16 -O2 -fanalyzer -c repro-minimal.cc

Compiler output:
repro-minimal.cc:14:32: warning: leak of file descriptor 'accept(*(const
Fd*)server.Fd::value_, 0, 0)' [CWE-775] [-Wanalyzer-fd-leak]

The diagnostic path reports that close can throw. The warning points to the
call that closes the descriptor.

Expected result:
The analyzer does not report a leak on this exception edge. The destructor is
implicitly noexcept, so an exception from close would call std::terminate.

Controls:
With -fno-exceptions, the line 14 warning disappears. The independent
caller-owned-fd warning at line 27 remains.
With -fanalyzer-assume-nothrow, the line 14 warning also disappears.
On glibc, the diagnostic paths contain no throwing-edge event because close is
declared nothrow.

Versions tested:
GCC 16.1.0 reproduces this warning on aarch64-apple-darwin24.
GCC master commit 475e9efffaf8de781d7e17b687faf1807e104b01 was inspected, not
run on Darwin. Its assumed-not-to-throw list still contains only fclose, and
the exception unwinder has no noexcept termination check.
The testcase compiles cleanly with -Wall -Wextra when -fanalyzer is absent.

Environment:
Using built-in specs.
COLLECT_GCC=g++-16
COLLECT_LTO_WRAPPER=/Users/bjorn/.gcc/versions/16.1.0/libexec/gcc/aarch64-apple-darwin24/16.1.0/lto-wrapper
Target: aarch64-apple-darwin24
Configured with: ../gcc-16.1.0/configure
--prefix=/Users/bjorn/.gcc/versions/16.1.0 --enable-languages=c,c++
--disable-nls --enable-checking=release --program-suffix=-16 --with-system-zlib
--build=aarch64-apple-darwin24
--with-sysroot=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.1.0 (GCC)

PR 122623 added -fanalyzer-assume-nothrow as a broad workaround for unannotated
external functions. It did not model noexcept termination or this default-C++
Darwin case.

Reply via email to