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

From: Andrey Rachitskiy <pl0h0yp1(at)gmail(dot)com>
To: malis(at)pgrust(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org
Subject: Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"
Date: 2026-08-04 10:40:54
Message-ID: CAB8bMit6VM6O68nEJG1e_eG3uGQBtASfTVK0D0yg==Ot7vuR5Q@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

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(at)gmail(dot)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(at)gmail(dot)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(at)postgresql(dot)org
>> >:
>>
>>> The following bug has been logged on the website:
>>>
>>> Bug reference: 19593
>>> Logged by: Michael Malis
>>> Email address: malis(at)pgrust(dot)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
>

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message David Rowley 2026-08-04 11:42:33 Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"
Previous Message Álvaro Herrera 2026-08-04 09:48:32 Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`