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

            Bug ID: 93934
           Summary: Unnecessary fld of uninitialized float stack variable
                    results in ub of valid C++ code
           Product: gcc
           Version: 7.4.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: vajdaz at protonmail dot com
  Target Milestone: ---

Valid C++ code is compiled to assembly that seems to be buggy. Below source
code and assembly output of the compiler.

Contents of source file float.cc:

extern void foo();
extern void bar();

double func(int size, double d, bool b) {
    double result;

    for(int i = 0; i < size; ++i) {
        if (i == 0) {
            foo();
            if(b) result = d;
        }
        else {
            bar();
            if(b) result = 42.0;
        }
    }

    return b && size > 0 ? result : 7.0;
}

I assume that this is valid C++ code. On C++ level there is no way to access an
uninitialized variable. The variable result is only used as a return value if
size is greater than zero, and b is true, in which case result will be
initialized before, for sure.

Create assembly:

$ g++ -m32 -O2 -fno-pic -fno-omit-frame-pointer -S float.cc

Output is:

        .file   "float.cc"
        .text
        .p2align 4,,15
        .globl  _Z4funcidb
        .type   _Z4funcidb, @function
_Z4funcidb:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        subl    $44, %esp
        .cfi_offset 7, -12
        .cfi_offset 6, -16
        .cfi_offset 3, -20
        movl    8(%ebp), %ebx
        movl    20(%ebp), %eax
        fldl    12(%ebp)
        testl   %ebx, %ebx
        fstpl   -48(%ebp)
        movl    %eax, -36(%ebp)
        jle     .L2
        movl    %eax, %esi
        xorl    %edi, %edi
        .p2align 4,,10
        .p2align 3
.L5:
        testl   %edi, %edi
        je      .L16
        call    _Z3barv
        movl    %esi, %eax
        testb   %al, %al
        je      .L4
        flds    .LC0
        fstpl   -32(%ebp)
.L4:
        addl    $1, %edi
        cmpl    %edi, %ebx
        jne     .L5
        cmpb    $0, -36(%ebp)
        fldl    -32(%ebp)
        je      .L17
        addl    $44, %esp
        popl    %ebx
        .cfi_remember_state
        .cfi_restore 3
        popl    %esi
        .cfi_restore 6
        popl    %edi
        .cfi_restore 7
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .p2align 4,,10
        .p2align 3
.L17:
        .cfi_restore_state
        fstp    %st(0)
.L2:
        flds    .LC1
        addl    $44, %esp
        popl    %ebx
        .cfi_remember_state
        .cfi_restore 3
        popl    %esi
        .cfi_restore 6
        popl    %edi
        .cfi_restore 7
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .p2align 4,,10
        .p2align 3
.L16:
        .cfi_restore_state
        call    _Z3foov
        fldl    -48(%ebp)
        movl    %esi, %eax
        testb   %al, %al
        fldl    -32(%ebp)
        fcmovne %st(1), %st
        fstp    %st(1)
        fstpl   -32(%ebp)
        jmp     .L4
        .cfi_endproc
.LFE0:
        .size   _Z4funcidb, .-_Z4funcidb
        .section        .rodata.cst4,"aM",@progbits,4
        .align 4
.LC0:
        .long   1109917696
        .align 4
.LC1:
        .long   1088421888
        .ident  "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
        .section        .note.GNU-stack,"",@progbits



Following things happen in the assembly code when size is 1 and b is true for
example.

* The value of size is stored into %ebx
* The value of b is stored into %eax and -36(%ebp), later in %esi
* The value of d is loaded into the fpu register stack and stored into
-48(%ebp)

We continue straight to the line with "je .L16" and jump to the label .L16. And
here comes the interesting part:

.L16:
        .cfi_restore_state
        call    _Z3foov
        fldl    -48(%ebp)    <-- loading value of d into fpu
        movl    %esi, %eax
        testb   %al, %al     <-- testing if b is true (for fcmovne below)
        fldl    -32(%ebp)    <-- loading uninitialized stack variable result
        fcmovne %st(1), %st  <-- choosing which value to put back into result
        fstp    %st(1)
        fstpl   -32(%ebp)
        jmp     .L4

Why we have here a combination of two fldl instructions and a fcmovne? This
causes a possible fpu exception if the value of the uninitialized variable
named result is accidentally a SNaN value. Which means, the behavior of this
code is unpredictable.

This can be reproduced with any GCC since 4.4.7, if the -march option is set to
something that supports fcmov instructions. I used GCC 7.4.0.

Is this a bug, or do I miss something?

Reply via email to