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

--- Comment #4 from Omer Ozarslan <omerfaruko at gmail dot com> ---
I debugged this a bit yesterday. __parse_integer returns nullptr for the second
argument here:

https://github.com/gcc-mirror/gcc/blame/62f8a246bbaa1a1f5aedba4c84f7fe4c7eca799f/libstdc%2B%2B-v3/include/std/format#L422-L450

          if (__detail::__from_chars_alnum<true>(__first, __last, __val, 10)
                && __first != __start) [[likely]]
            return {__val, __first};

[...]

      return {0, nullptr};

and the caller in turn throws here:

https://github.com/gcc-mirror/gcc/blob/62f8a246bbaa1a1f5aedba4c84f7fe4c7eca799f/libstdc%2B%2B-v3/include/std/format#L656-L659

            auto [__v, __ptr] = __format::__parse_integer(__first, __last);
            if (!__ptr)
              __throw_format_error("format error: invalid width or precision "
                                   "in format-spec");

The interesting bit is that __from_chars_alnum returns with __val = 1. But
despite that, __first doesn't advance past __start and __first != __start
returns false.

So why doesn't it advance although __first is taken by reference in
__from_chars_alnum?

I confirmed __parse_integer emits different ASM for module version vs include
version. For some reason, it seems like the __first reference passed to
__from_chars_alnum is not the same as the __first reference compared to __start
in the same check, i.e. some other address in the stack initialized with the
value of __first is what's passed by reference to __from_chars_alnum. (Attached
extracted ASMs for bad and good versions.)

Then I figured GCC can also dump trees. The tree with module version (bad one)
has this interesting thing going on:

struct pair std::__format::__parse_integer<char> (const char * __first, const
char * __last)
{
  bool retval.119;
  bool iftmp.120;
  const char * __first.121;    // Notice this
  struct pair D.173282;
  struct pair D.118538;
  const char * __first.122;
  struct pair D.118539;
  int D.118540;
  <<< Unknown tree: nullptr_type >>> D.118541;

[...]

          __first.121 = __first;   // __first.121 is passed by reference below.
          _1 = std::__detail::__from_chars_alnum<true, short unsigned int>
(&__first.121, __last, &__val, 10);
          if (_1 != 0) goto <D.173278>; else goto <D.173275>;
          <D.173278>:              // but __first is compared below
          if (__first != __start) goto <D.173279>; else goto <D.173275>;


This is different from include version:

struct pair std::__format::__parse_integer<char> (const char * __first, const
char * __last)
{
  bool retval.116;
  bool iftmp.117;
  struct pair D.135974;
  struct pair D.94694;
  struct pair D.94727;
  int D.94720;
  <<< Unknown tree: nullptr_type >>> D.94721;

[...]

          _2 = std::__detail::__from_chars_alnum<true, short unsigned int>
(&__first, __last, &__val, 10);
          if (_2 != 0) goto <D.135970>; else goto <D.135968>;
          <D.135970>:
          __first.118_3 = __first;
          if (__start != __first.118_3) goto <D.135971>; else goto <D.135968>;



I don't know why would that happen. Full trees were too large to attach even
compressed.

Reply via email to