Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"

Поиск
Список
Период
Сортировка
Искать
От
Andrey Rachitskiy
Тема
Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"
Дата
в 13:40:54
Msg-id
CAB8bMit6VM6O68nEJG1e_eG3uGQBtASfTVK0D0yg==Ot7vuR5Q@mail.gmail.com
Список
Дерево обсуждения
Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow" Andrey Rachitskiy <pl0h0yp1@gmail.com>
I researched related past bugs and found this is already fixed in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126464 (reverse Inf handling in float_widen_lhs_range / range-op-float.cc).

Jakub Jelinek says: Fixed also for 15.4+, as well as backported to 14.5 and 13.5.

вт, 4 авг. 2026 г. в 13:05, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Hi Michael, all,

I've opened PR in Bugzilla GCC for the issue you reported:

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

I'll follow up there with any progress.

сб, 1 авг. 2026 г. в 20:16, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Hi, Michael!

Agreed — PostgreSQL's check is fine, this is a gcc wrong-code bug.

circle_ar() is just two inlined float8_mul() calls:
```
    return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
```
For radius 1e154, r*r is still finite (~1e308), but (r*r)*pi overflows.
float8_mul() is supposed to catch that with:
```
    result = val1 * val2;
    if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
        float_overflow_error();
```
That check is present in the source and survives at -O0, with clang,
and for the equivalent SQL expression
1e154::float8 * 1e154::float8 * pi() (as in the report).  With gcc -O1
and above it is deleted for the outer multiply.

What gcc does is jump threading (-fthread-jumps; enabled by default at
-O1 and above).  After the inner check it knows r*r is finite, M_PI is
a finite constant, and it incorrectly treats "finite * finite yields
Inf" as impossible.  The outer float_overflow_error() call disappears;
the function just returns Infinity.  -O2 -fno-thread-jumps restores the
correct behaviour.  This is with a normal -O2 build; we do not use
-ffinite-math-only / -ffast-math, so gcc is not entitled to assume that
finite*finite cannot produce Infinity.

Reduced C reproducer (same shape as circle_ar / float8_mul).  On the
correct path it prints the same message as PostgreSQL:
```
    ERROR:  value out of range: overflow
```
On the buggy path it prints Infinity (and exits 1):
```
    Host:  Linux x86_64
    gcc:   gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0
    clang: Ubuntu clang version 21.1.8

    compiler / flags                 result
    -------------------------------  ------------------------------------
    gcc   -O0                        OK   (ERROR: value out of range...)
    gcc   -O1                        BUG  (Infinity)
    gcc   -O2                        BUG  (Infinity)
    gcc   -O2 -fno-thread-jumps      OK   (ERROR: value out of range...)
    clang -O0                        OK   (ERROR: value out of range...)
    clang -O1                        OK   (ERROR: value out of range...)
    clang -O2                        OK   (ERROR: value out of range...)
```
So this is not "PostgreSQL does not check circle areas".  The check is
there, gcc optimizes it away, clang does not.  The same pattern can
affect other inlined float4/8 helpers when both operands are proven
finite.

One possible workaround on our side would be to store the operation
result in a volatile temporary in those helpers, so the isinf() check
must inspect the computed value.  But it seems to me that we should
address the root cause first: file this with gcc bugzilla and decide
what, if anything, to do in PostgreSQL based on their response.

сб, 1 авг. 2026 г. в 18:02, PG Bug reporting form <noreply@postgresql.org>:
The following bug has been logged on the website:

Bug reference:      19593
Logged by:          Michael Malis
Email address:      malis@pgrust.com
PostgreSQL version: 18.3
Operating system:   Debian (official Docker image), aarch64
Description:       

I'm not sure what you'll want to do with this one, but I figured I would at
least report it. The cause seems to be a bug in gcc.

area(circle) returns Infinity where it must raise ERROR 22003 "value out of
range: overflow".  The overflow check in float8_mul() is present in the
source
but is absent from the generated code, because gcc 13 and later delete it at
-O1 and above.

The same binary raises the error correctly for the equivalent SQL-level
expression, and for a circle whose radius overflows one step earlier, so
this
is not "PostgreSQL does not check circle areas".

    -- WRONG: no error, returns Infinity
    SELECT area(circle '<(0,0),1e154>');
                      area
    ------------------------
                   Infinity

    -- CORRECT (control): the *inner* multiply overflows, so the surviving
    -- check fires
    SELECT area(circle '<(0,0),1e200>');
    ERROR:  value out of range: overflow

    -- CORRECT (control): the same arithmetic, expressed in SQL
    SELECT 1e154::float8 * 1e154::float8 * pi();
    ERROR:  value out of range: overflow

    -- sane value, for reference
    SELECT area(circle '<(0,0),1e10>');
           area
    -------------------------
     3.1415926535897933e+20

1e154 * 1e154 = 1e308, which is finite (below DBL_MAX); multiplying that by
pi
overflows.  Behaviour is identical whether the expression is constant-folded
at
plan time or evaluated at runtime:

    SELECT area(c) FROM (VALUES (circle '<(0,0),1e154>')) t(c);   --
Infinity

NaN and Infinity radii behave correctly (NaN -> NaN, Infinity -> Infinity).


WHERE IT COMES FROM

src/backend/utils/adt/geo_ops.c:5159

    static float8
    circle_ar(CIRCLE *circle)
    {
        return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
    }

src/include/utils/float.h:207

    static inline float8
    float8_mul(const float8 val1, const float8 val2)
    {
        float8      result;

        result = val1 * val2;
        if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
            float_overflow_error();
        ...

Two float8_mul calls are inlined into one function.  gcc keeps the first
copy's
overflow check and deletes the second's.

Disassembly of the shipped binary (circle_area; symbols are present in
.dynsym).
gcc lowers isinf(x) to |x| > DBL_MAX, with d30 = 0x7fefffffffffffff:

    ; inner multiply -- check intact
    5540dc  fmul d31, d29, d29        ; r*r
    5540e0  fcmp d31, d30
    5540e4  b.le 5540fc
    5540e8  fabs d29, d29             ; |r|  <- the !isinf(val1) test
    5540ec  fcmp d29, d30
    5540f0  b.le 554154
    554154  bl   float_overflow_error ; raises

    ; outer multiply -- operand test gone
    554104  adrp x0, 76c000
    554108  ldr  d29, [x0, #568]      ; M_PI
    55410c  fmul d31, d31, d29        ; (r*r) * M_PI
    554110  fcmp d31, d30
    554114  b.le 554120
    554118  mov  x0, #0x7ff0000000000000   ; returns +Infinity
    55411c  b    55412c

Control reaches the outer multiply only via the b.le at 5540e4, i.e. only
when
r*r <= DBL_MAX, and M_PI is a finite constant.  So
"!isinf(val1) && !isinf(val2)" is true on that path and
float_overflow_error()
must be called.

Building from an unmodified 18.3 tree (git tag stamp 62d6c7d) with gcc 14.2
and
the same CFLAGS reproduces it, so this is not specific to Debian's
packaging:

    ./configure --without-readline --without-zlib --without-icu \
        CFLAGS="-g -O2 -fno-strict-aliasing -fwrapv
-fexcess-precision=standard"
    make -C src/backend submake-generated-headers
    make -C src/backend/utils/adt geo_ops.o
    objdump -d geo_ops.o

circle_area then contains 3 fmul but only 1 call to float_overflow_error.
From
pristine source the outer check is removed entirely: there is no DBL_MAX
comparison after the second fmul at all, only the underflow test.






--
Regards,
Rachitskiy Andrey


--
Regards,
Rachitskiy Andrey
В списке pgsql-bugs по дате отправления
От: Álvaro Herrera
Дата:
От: PG Bug reporting form
Дата:
FAQ